use self::section_map::FunctionInfo;
use self::section_map::IndexedLayout;
use self::section_map::InputSectionId;
use self::section_map::SymbolInfo;
use crate::Binary;
use crate::Diff;
use crate::DiffValues;
use crate::ElfFile64;
use crate::Report;
use crate::Result;
use crate::SectionCoverage;
use crate::arch::Arch;
use crate::arch::Instruction;
use crate::arch::PltEntry;
use crate::arch::RType;
use crate::arch::Relaxation;
use crate::arch::RelaxationKind;
use crate::diagnostics::TraceOutput;
use crate::get_r_type;
use crate::section_map;
use anyhow::Context as _;
use anyhow::anyhow;
use anyhow::bail;
use anyhow::ensure;
use colored::ColoredString;
use colored::Colorize as _;
use hashbrown::HashMap;
use itertools::Itertools as _;
use linker_utils::elf::BitMask;
use linker_utils::elf::DynamicRelocationKind;
use linker_utils::elf::RelocationKind;
use linker_utils::elf::RelocationKindInfo;
use linker_utils::elf::RelocationSize;
#[allow(clippy::wildcard_imports)]
use linker_utils::elf::secnames::*;
use linker_utils::relaxation::RelocationModifier;
use linker_utils::utils::u32_from_slice;
use object::LittleEndian;
use object::Object as _;
use object::ObjectKind;
use object::ObjectSection as _;
use object::ObjectSymbol as _;
use object::RelocationTarget;
use object::SectionKind;
use object::read::elf::ElfSection64;
use object::read::elf::FileHeader as _;
use object::read::elf::ProgramHeader as _;
use object::read::elf::SectionHeader as _;
use std::fmt::Display;
use std::fmt::Write as _;
use std::iter::Peekable;
use std::ops::Range;
const SHOW_FUNCTION_ENV: &str = "LINKER_DIFF_FOCUS_FUNCTION";
const SUPPORTED_SECTION_KINDS: &[SectionKind] = &[SectionKind::Text, SectionKind::Data];
pub(crate) fn report_section_diffs<A: Arch>(report: &mut Report, binaries: &[Binary]) {
let Some(layout) = binaries[0].indexed_layout.as_ref() else {
report.add_error("A .layout file is required");
return;
};
if binaries
.iter()
.any(|o| o.address_index.index_error.is_some())
{
return;
}
if let Some(cov) = report.coverage.as_mut() {
populate_section_coverage(cov, layout);
}
let by_name = symbol_versions_by_name(binaries, layout);
let matched_sections = unified_sections_from_symbols(report, by_name, layout, binaries);
let mut section_ids_to_process = matched_sections
.keys()
.copied()
.sorted()
.collect_vec();
while let Some(section_id) = section_ids_to_process.pop() {
let section_versions = matched_sections.get(§ion_id).unwrap();
let section_diff_key = format!(
"section-diff-failed.{}",
section_versions
.original_section(layout)
.and_then(|s| Ok(s.name()?))
.unwrap_or("unknown-section")
);
if !report.should_ignore(§ion_diff_key)
&& let Err(error) = compare_sections::<A>(report, section_versions, binaries, layout)
{
report.add_diff(Diff {
key: section_diff_key,
values: DiffValues::PreFormatted(error.to_string()),
});
}
}
}
fn compare_sections<A: Arch>(
report: &mut Report,
section_versions: &SectionVersions<'_>,
binaries: &[Binary],
layout: &IndexedLayout,
) -> Result {
let original_section = section_versions.original_section(layout)?;
if let Some(coverage) = report.coverage.as_mut()
&& let Some(sec_cov) = coverage
.sections
.get_mut(§ion_versions.input_section_id)
{
sec_cov.diffed = true;
}
if determine_output_section_kind(binaries, §ion_versions.addresses_by_binary)
.is_some_and(|output_section_kind| !SUPPORTED_SECTION_KINDS.contains(&output_section_kind))
{
return Ok(());
}
let mut testers = binaries
.iter()
.zip(§ion_versions.addresses_by_binary)
.map(|(bin, §ion_address)| -> Result<RelaxationTester> {
RelaxationTester::new(
&original_section,
bin,
section_address,
layout.input_file_for_section(section_versions.input_section_id),
)
})
.collect::<Result<Vec<_>>>()?;
let section_kind = original_section.kind();
let mut relocations = original_section.relocations().collect_vec();
relocations.sort_by_key(|(offset, _)| *offset);
let mut relocations = relocations.into_iter().peekable();
while let Some(group) = RelocationGroup::<A>::next(&mut relocations, section_versions, layout)?
{
diff_literal_bytes::<A>(
report,
section_versions,
layout,
&mut testers,
group
.start_offset()
.saturating_sub(A::MAX_RELAX_MODIFY_BEFORE),
)?;
if testers
.iter()
.any(|t| t.next_modifier == RelocationModifier::SkipNextRelocation)
{
testers
.iter_mut()
.for_each(|t| t.next_modifier = RelocationModifier::Normal);
continue;
}
let mut resolutions = Vec::new();
for tester in &mut testers {
resolutions.push(tester.resolve_group_traced(section_kind, &group));
}
if let Some(first) = resolutions.first() {
let mut trace = TraceOutput::default();
let at_least_one_match = crate::diagnostics::trace_scope(&mut trace, || {
resolutions[1..].iter().any(|other| first.matches(other))
});
let first_has_match_failure = first.relaxations.is_none() || first.has_error();
if !at_least_one_match || first_has_match_failure {
let start_offset = group.start_offset();
let original_annotations = group.into_original_annotations();
let bin_attributes = testers[1].bin.address_index.bin_attributes;
let diff_key =
diff_key_for_res_mismatch(&resolutions, &original_annotations, bin_attributes);
if !report.should_ignore(&diff_key) {
let diff = resolution_diff_exec(
&diff_key,
start_offset,
original_annotations,
&resolutions,
&testers,
section_versions.input_section_id,
layout,
trace,
)?;
report.add_diff(diff);
}
update_offsets_if_match_failed(
section_versions,
layout,
original_section.size(),
&mut testers,
&resolutions,
&mut relocations,
)?;
}
};
}
diff_literal_bytes::<A>(
report,
section_versions,
layout,
&mut testers,
original_section.size(),
)?;
Ok(())
}
fn determine_output_section_kind(
binaries: &[Binary<'_>],
addresses_by_binary: &[u64],
) -> Option<SectionKind> {
binaries
.iter()
.zip(addresses_by_binary)
.rev()
.find_map(|(bin, address)| {
bin.section_containing_address(*address)
.map(|section| section.kind())
})
}
fn update_offsets_if_match_failed<A: Arch>(
section_versions: &SectionVersions<'_>,
layout: &IndexedLayout<'_>,
section_size: u64,
testers: &mut Vec<RelaxationTester<'_>>,
resolutions: &[ResolvedGroup<'_, A>],
relocations: &mut Peekable<std::vec::IntoIter<(u64, object::Relocation)>>,
) -> Result {
if resolutions.iter().any(|r| {
r.annotations
.iter()
.any(|a| matches!(a.kind, AnnotationKind::MatchFailed(..)))
}) {
let offset = testers.iter().map(|t| t.previous_end).max().unwrap_or(0);
let section_info = layout
.get_section_info(section_versions.input_section_id)
.context("Attempt to diff missing section")?;
let new_offset = section_info
.next_function_offset(offset)
.unwrap_or(section_size);
testers.iter_mut().for_each(|t| t.previous_end = new_offset);
while let Some((next_rel_offset, _rel)) = relocations.peek() {
if *next_rel_offset < new_offset {
relocations.next();
} else {
break;
}
}
};
Ok(())
}
struct RelocationGroup<'data, A: Arch> {
relocations: Vec<InputRelocation<'data, A>>,
}
#[derive(Debug, PartialEq, Eq)]
struct RelaxationGroup<'data, A: Arch> {
match_results: Vec<RelaxationMatchResult<'data, A>>,
is_complete: bool,
}
impl<'data, A: Arch> RelaxationGroup<'data, A> {
fn start_offset(&self) -> u64 {
self.match_results
.iter()
.map(|r| r.start())
.min()
.unwrap_or(0)
}
fn end_offset(&self) -> u64 {
self.match_results
.iter()
.map(|r| r.end())
.max()
.unwrap_or(0)
}
fn first_if_matched(&self) -> Option<&RelaxationMatch<'data, A>> {
if !self.match_results.iter().all(|m| m.matched()) {
return None;
}
match self.match_results.first()? {
RelaxationMatchResult::Matched(m) => Some(m),
_ => None,
}
}
fn eliminate_alt_r_types(&mut self, reference: &Reference<A::RType>) {
for result in &mut self.match_results {
if let RelaxationMatchResult::Matched(m) = result
&& let Some(alt_r_type) = m.relaxation.alt_r_type
{
match (
reference.verify_consistent_with_r_type(m.relaxation.new_r_type),
reference.verify_consistent_with_r_type(alt_r_type),
) {
(Err(_), Ok(())) => {
m.relaxation.new_r_type = alt_r_type;
}
(Ok(()), Err(_)) => {
m.relaxation.alt_r_type = None;
}
_ => {}
}
}
}
}
fn matches_if_ok(&self) -> Option<Vec<RelaxationMatch<'data, A>>> {
self.match_results
.iter()
.map(|r| match r {
RelaxationMatchResult::Matched(relaxation_match) => Ok(*relaxation_match),
_ => Err(()),
})
.collect::<Result<Vec<RelaxationMatch<A>>, ()>>()
.ok()
}
fn matches_skipping_nops(&self) -> Vec<&RelaxationMatchResult<'data, A>> {
self.match_results
.iter()
.filter(|result| !matches!(result, RelaxationMatchResult::Matched(m) if m.relaxation.relaxation_kind.is_replace_with_no_op()))
.collect()
}
}
impl<'data, A: Arch> RelocationGroup<'data, A> {
fn into_original_annotations(self) -> Vec<OriginalAnnotation<'data, A>> {
self.relocations
.into_iter()
.map(|r| r.original_annotation)
.collect()
}
fn next(
relocations_in: &mut Peekable<std::vec::IntoIter<(u64, object::Relocation)>>,
section_versions: &SectionVersions<'data>,
layout: &IndexedLayout<'data>,
) -> Result<Option<RelocationGroup<'data, A>>> {
let mut relocations = Vec::new();
let mut chain = Vec::new();
while let Some(next_r_type) = relocations_in
.peek()
.map(|(_, rel)| get_r_type::<A::RType>(rel))
{
chain.push(next_r_type);
if chain.len() >= 2 && !A::should_chain_relocations(&chain) {
break;
}
let (offset, rel) = relocations_in.next().unwrap();
let original_annotation =
get_original_annotation::<A>(section_versions, layout, &rel, offset)?;
relocations.push(InputRelocation {
offset,
rel,
original_annotation,
});
}
if relocations.is_empty() {
Ok(None)
} else {
Ok(Some(RelocationGroup { relocations }))
}
}
fn start_offset(&self) -> u64 {
self.relocations[0].offset
}
fn is_complete_group(&self) -> bool {
A::is_complete_chain(self.relocations.iter().map(|r| get_r_type(&r.rel)))
}
}
struct InputRelocation<'data, A: Arch> {
offset: u64,
rel: object::Relocation,
original_annotation: OriginalAnnotation<'data, A>,
}
impl<'data, A: Arch> InputRelocation<'data, A> {
fn original_referent(&self) -> Referent<'data, <A as Arch>::RType> {
self.original_annotation.reference.referent
}
}
fn get_original_annotation<'data, A: Arch>(
section_versions: &SectionVersions<'data>,
layout: &IndexedLayout<'data>,
rel: &object::Relocation,
offset: u64,
) -> Result<OriginalAnnotation<'data, A>> {
let mut orig_trace = TraceOutput::default();
let original_referent = crate::diagnostics::trace_scope(&mut orig_trace, || {
get_original_referent(
rel,
layout.input_file_for_section(section_versions.input_section_id),
)
})?;
let original_annotation = OriginalAnnotation {
success: MatchedRelaxation::<A> {
r_type: get_r_type(rel),
relaxation_kind: None,
},
reference: Reference {
referent: original_referent,
indirection: Indirection::default(),
},
trace: orig_trace,
offset,
};
Ok(original_annotation)
}
fn diff_literal_bytes<'data, A: Arch>(
report: &mut Report,
section_versions: &SectionVersions<'data>,
layout: &IndexedLayout<'data>,
testers: &mut [RelaxationTester<'data>],
end: u64,
) -> Result {
let start = testers
.iter()
.map(|t| t.previous_end)
.max()
.unwrap_or_default();
if end <= start {
return Ok(());
}
let mut ok = true;
for tester in testers.iter_mut() {
tester.previous_end = start;
if end > tester.previous_end {
ok &= tester.is_equal_up_to(end);
}
}
if ok {
for tester in testers {
tester.previous_end = end;
}
} else {
let resolutions = testers
.iter()
.map(|_| ResolvedGroup {
relaxations: None,
annotations: vec![Annotation {
offset_in_section: start,
kind: AnnotationKind::<A>::LiteralByteMismatch,
}],
reference: Reference::unknown(),
start,
end,
trace: TraceOutput::default(),
})
.collect_vec();
let key = diff_key_for_res_mismatch(
&resolutions,
&[],
testers[1].bin.address_index.bin_attributes,
);
if !report.should_ignore(&key) {
report.add_diff(resolution_diff_exec(
&key,
end,
vec![],
&resolutions,
testers,
section_versions.input_section_id,
layout,
TraceOutput::default(),
)?);
}
}
Ok(())
}
struct ExecDiff<'data, A: Arch> {
offset: u64,
original_annotations: Vec<OriginalAnnotation<'data, A>>,
resolutions: &'data [ResolvedGroup<'data, A>],
testers: &'data [RelaxationTester<'data>],
section_id: InputSectionId,
trace: TraceOutput,
}
impl<A: Arch> ExecDiff<'_, A> {
fn write_to(&self, f: &mut String, layout: &IndexedLayout) -> Result {
let original_section = layout.get_elf_section(self.section_id)?;
let file_identifier = layout.input_filename_for_section(self.section_id);
let function_info = layout
.get_section_info(self.section_id)
.context("Attempted to diff a section that wasn't emitted")?
.function_at_offset(self.offset, layout)?;
let range = self.resolutions.iter().map(|r| r.start).min().unwrap_or(0)
..self.resolutions.iter().map(|r| r.end).max().unwrap_or(0);
writeln!(
f,
"{file_identifier} {section_name} {function_name}",
section_name = original_section.name()?.blue(),
function_name = String::from_utf8_lossy(function_info.name).cyan()
)?;
let mut trace = TraceOutput::default();
let annotations = self
.original_annotations
.iter()
.map(|orig| {
let annotation = Annotation {
offset_in_section: orig.offset,
kind: AnnotationKind::MatchedRelaxation(orig.success.clone()),
};
trace.append(orig.trace.clone());
annotation
})
.collect_vec();
let mut blocks = vec![RelocationInstructionBlock {
name: ORIG,
annotations,
reference: self.original_annotations.last().map(|a| a.reference),
trace_messages: Vec::new(),
section_bytes: original_section.data().ok(),
section_size: original_section.size(),
section_address: 0,
range: range.start..range.end,
function_info,
instructions: Default::default(),
trace,
}];
for (res, tester) in self.resolutions.iter().zip(self.testers) {
let section_bytes = tester.section_bytes;
let block = RelocationInstructionBlock {
name: &tester.bin.name,
annotations: res.annotations.clone(),
reference: Some(res.reference),
trace_messages: tester.bin.trace.messages_in(
range.start + tester.section_address..range.end + tester.section_address,
),
section_bytes,
section_size: tester.section_size,
section_address: tester.section_address,
range: range.start..range.end,
function_info,
instructions: Default::default(),
trace: res.trace.clone(),
};
blocks.push(block);
}
if original_section.kind() == SectionKind::Text {
for block in &mut blocks {
let mut trace = TraceOutput::default();
crate::diagnostics::trace_scope(&mut trace, || {
block.decode_instructions();
});
block.trace.append(trace);
}
}
let maximum_widths = blocks.iter().fold(ColumnWidths::default(), |widths, b| {
widths.merge(b.widths())
});
for block in &blocks {
block.write_to(f, &maximum_widths)?;
}
for message in &self.trace.messages {
write!(f, " {message}")?;
}
Ok(())
}
}
fn resolution_diff_exec<A: Arch>(
key: &str,
offset: u64,
original_annotations: Vec<OriginalAnnotation<A>>,
resolutions: &[ResolvedGroup<A>],
testers: &[RelaxationTester<'_>],
section_id: InputSectionId,
layout: &IndexedLayout,
trace: TraceOutput,
) -> Result<Diff> {
let diff = ExecDiff {
offset,
original_annotations,
resolutions,
testers,
section_id,
trace,
};
let mut out = String::new();
diff.write_to(&mut out, layout)?;
Ok(Diff {
key: key.to_string(),
values: DiffValues::PreFormatted(out),
})
}
fn get_original_referent<'data, R: RType>(
rel: &object::Relocation,
input_file: &crate::section_map::InputFile<'data>,
) -> Result<Referent<'data, R>> {
if let RelocationTarget::Symbol(symbol_index) = rel.target() {
let symbol = input_file.elf_file.symbol_by_index(symbol_index)?;
if let Some(section_index) = symbol.section_index() {
let section = input_file.elf_file.section_by_index(section_index)?;
let flags = section.elf_section_header().sh_flags(LittleEndian) as u32;
if flags & object::elf::SHF_MERGE != 0 && flags & object::elf::SHF_STRINGS != 0 {
let section_data = section.data()?;
let string_plus_rest = §ion_data[symbol.address() as usize..];
if let Some(end_offset) = memchr::memchr(0, string_plus_rest) {
let addend = symbol
.name_bytes()
.is_ok_and(|name| !name.is_empty())
.then(|| rel.addend());
return Ok(Referent::MergedString(MergedStringRef {
data: &string_plus_rest[..end_offset],
named_symbol_addend: addend,
}));
}
}
}
let name_bytes = symbol.name_bytes()?;
let name = SymbolName {
bytes: name_bytes,
version: None,
};
return Ok(Referent::Named(name, rel.addend()));
}
Ok(Referent::Unknown)
}
fn diff_key_for_res_mismatch<A: Arch>(
resolutions: &[ResolvedGroup<A>],
original_annotations: &[OriginalAnnotation<A>],
bin_attributes: BinAttributes,
) -> String {
if resolutions.len() < 2 {
return "missing-resolutions".to_owned();
}
match (
&resolutions[0].reference.referent,
&resolutions[1].reference.referent,
) {
(Referent::DynamicRelocation(d), Referent::Undefined(unmatched)) => {
if d.entry.is_weak && unmatched.address == 0 {
return format!("rel.undefined-weak.dynamic.{}", d.r_type);
}
}
(Referent::DynamicRelocation(ours), Referent::DynamicRelocation(theirs)) => {
if !resolutions[0].reference.indirection.is_via_plt()
&& resolutions[1].reference.indirection.is_via_plt()
&& ours.addend == 0
&& theirs.addend == 0
&& ours.entry == theirs.entry
{
return "rel.dynamic-plt-bypass".to_owned();
}
}
(Referent::Named(ours, _), Referent::Undefined(_)) => {
return format!("rel.extra-symbol.{ours}");
}
(Referent::Named(_, _), Referent::DynamicRelocation(_)) => {
if resolutions[0].reference.indirection == Indirection::Got
&& resolutions[1].reference.indirection == Indirection::Got
{
return format!("rel.missing-got-dynamic.{}", bin_attributes.output_kind);
}
}
_ => {}
}
if resolutions[0]
.reference
.referent
.matches(resolutions[1].reference.referent)
&& resolutions[0].reference.indirection == Indirection::Got
&& resolutions[1].reference.indirection == Indirection::GotPltGot
{
return "rel.missing-got-plt-got".to_owned();
}
let reference = resolutions
.iter()
.skip(1)
.find_map(|r| r.relaxations.as_ref().and_then(|r| r.first_if_matched()));
let ours = resolutions[0]
.relaxations
.as_ref()
.and_then(|r| r.first_if_matched());
match (ours, reference) {
(Some(r1), Some(r2)) => {
let Some(orig) = original_annotations.first() else {
return "missing-original".to_owned();
};
match (
r1.relaxation.relaxation_kind.is_no_op(),
r2.relaxation.relaxation_kind.is_no_op(),
) {
(true, false) => {
format!(
"rel.missing-opt.{}.{:?}.{}",
orig.success.r_type,
r2.relaxation.relaxation_kind,
bin_attributes.type_name()
)
}
(false, true) => {
format!(
"rel.extra-opt.{}.{:?}.{}",
orig.success.r_type,
r1.relaxation.relaxation_kind,
bin_attributes.type_name()
)
}
_ => {
let ours_is_copy = resolutions[0].reference.referent.is_copy_relocation();
let any_others_copy = resolutions[1..]
.iter()
.any(|r| r.reference.referent.is_copy_relocation());
if ours_is_copy && !any_others_copy {
format!("rel.extra-copy-relocation.{}", orig.success.r_type)
} else if !ours_is_copy && any_others_copy {
format!("rel.missing-copy-relocation.{}", orig.success.r_type)
} else {
format!(
"rel.{}.{}",
r1.relaxation.new_r_type, r2.relaxation.new_r_type
)
}
}
}
}
_ => {
let failure_kind = |r: &ResolvedGroup<A>| {
if r.annotations
.iter()
.any(|a| matches!(a.kind, AnnotationKind::LiteralByteMismatch))
{
Some("literal-byte-mismatch".to_owned())
} else {
r.annotations
.iter()
.zip(original_annotations)
.find_map(|(a, orig)| match &a.kind {
AnnotationKind::Ambiguous(_) => Some("rel.multiple_matches".to_owned()),
AnnotationKind::MatchFailed(_) => {
Some(format!("rel.match_failed.{}", orig.success.r_type))
}
AnnotationKind::MatchedRelaxation(_) => None,
AnnotationKind::LiteralByteMismatch => {
unreachable!();
}
AnnotationKind::Error(e) => Some(e.clone()),
})
}
};
failure_kind(&resolutions[0])
.or(failure_kind(&resolutions[1]))
.unwrap_or("rel.unknown_failure".to_owned())
}
}
}
struct RelocationInstructionBlock<'data, A: Arch> {
name: &'data str,
annotations: Vec<Annotation<'data, A>>,
reference: Option<Reference<'data, A::RType>>,
trace_messages: Vec<&'data str>,
section_bytes: Option<&'data [u8]>,
section_size: u64,
section_address: u64,
range: Range<u64>,
function_info: FunctionInfo<'data>,
instructions: Vec<Instruction<'data, A>>,
trace: TraceOutput,
}
struct OriginalAnnotation<'data, A: Arch> {
success: MatchedRelaxation<A>,
trace: TraceOutput,
offset: u64,
reference: Reference<'data, A::RType>,
}
#[derive(Clone, Debug)]
struct Annotation<'data, A: Arch> {
offset_in_section: u64,
kind: AnnotationKind<'data, A>,
}
#[derive(Clone, Debug)]
enum AnnotationKind<'data, A: Arch> {
MatchedRelaxation(MatchedRelaxation<A>),
Ambiguous(Vec<RelaxationMatch<'data, A>>),
MatchFailed(Vec<FailedMatch<A>>),
Error(String),
LiteralByteMismatch,
}
#[derive(Clone, Debug)]
struct MatchedRelaxation<A: Arch> {
r_type: A::RType,
relaxation_kind: Option<<A as Arch>::RelaxationKind>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
enum RelaxationMatchResult<'data, A: Arch> {
Matched(RelaxationMatch<'data, A>),
AllFailed(Vec<FailedMatch<A>>),
Ambiguous(Vec<RelaxationMatch<'data, A>>),
}
impl<'data, A: Arch> RelaxationMatchResult<'data, A> {
fn start(&self) -> u64 {
match self {
RelaxationMatchResult::Matched(relaxation_match) => relaxation_match.start,
RelaxationMatchResult::AllFailed(failed) => {
failed.iter().map(|m| m.start).min().unwrap_or(0)
}
RelaxationMatchResult::Ambiguous(matches) => {
matches.iter().map(|m| m.start).min().unwrap_or(0)
}
}
}
fn end(&self) -> u64 {
match self {
RelaxationMatchResult::Matched(relaxation_match) => relaxation_match.end,
RelaxationMatchResult::AllFailed(failed) => {
failed.iter().map(|m| m.end).max().unwrap_or(0)
}
RelaxationMatchResult::Ambiguous(matches) => {
matches.iter().map(|m| m.end).max().unwrap_or(0)
}
}
}
fn annotations(&self) -> Vec<Annotation<'data, A>> {
match self {
RelaxationMatchResult::Matched(relaxation_match) => {
vec![relaxation_match.annotation()]
}
RelaxationMatchResult::AllFailed(failed_matches) => {
failed_matches.iter().map(|r| r.annotation()).collect()
}
RelaxationMatchResult::Ambiguous(matches) => vec![Annotation {
offset_in_section: matches.first().unwrap().offset,
kind: AnnotationKind::Ambiguous(matches.clone()),
}],
}
}
fn matches(&self, b: &RelaxationMatchResult<'_, A>) -> bool {
match (self, b) {
(RelaxationMatchResult::Matched(a_match), RelaxationMatchResult::Matched(b_match)) => {
a_match.relaxation == b_match.relaxation
}
_ => false,
}
}
fn next_modifier(&self) -> RelocationModifier {
match self {
RelaxationMatchResult::Matched(m) => m.next_modifier,
_ => RelocationModifier::Normal,
}
}
fn matched(&self) -> bool {
matches!(self, RelaxationMatchResult::Matched(_))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct RelaxationMatch<'data, A: Arch> {
relaxation: Relaxation<A>,
value: u64,
start: u64,
end: u64,
original_referent: Referent<'data, <A as Arch>::RType>,
addend: i64,
offset: u64,
next_modifier: RelocationModifier,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct FailedMatch<A: Arch> {
candidate: Relaxation<A>,
reason: String,
offset: u64,
start: u64,
end: u64,
}
impl<A: Arch> FailedMatch<A> {
fn new(
candidate: Relaxation<A>,
reason: String,
offset: u64,
start: u64,
end: u64,
) -> FailedMatch<A> {
FailedMatch {
candidate,
reason,
offset,
start,
end,
}
}
fn annotation(&self) -> Annotation<'static, A> {
Annotation {
offset_in_section: self.offset,
kind: AnnotationKind::MatchFailed(vec![self.clone()]),
}
}
}
impl<A: Arch> RelocationInstructionBlock<'_, A> {
fn widths(&self) -> ColumnWidths {
ColumnWidths {
name: self.name.len(),
address: format!("{:x}", self.section_address + self.section_size).len(),
instruction_bytes: self
.instructions
.iter()
.map(|i| i.bytes.len())
.max()
.unwrap_or_default(),
}
}
fn decode_instructions(&mut self) {
let Some(section_bytes) = self.section_bytes else {
return;
};
self.instructions = A::decode_instructions_in_range(
section_bytes,
self.section_address,
self.function_info.offset_in_section,
self.range.clone(),
);
}
fn write_to(&self, f: &mut String, maximum_widths: &ColumnWidths) -> Result {
let name_width = maximum_widths.name;
let address_width = maximum_widths.address;
let mut annotations = self.annotations.iter().peekable();
for instruction in &self.instructions {
let instruction_offset = instruction.address() - self.section_address;
let instruction_end = instruction_offset + instruction.bytes.len() as u64;
write!(
f,
"{:name_width$} 0x{:0address_width$x}: [ ",
self.name.blue(),
instruction.address()
)?;
let mut offset = instruction.address() - self.section_address;
for v in instruction.bytes {
if self.range.contains(&offset) {
write!(f, "{} ", format!("{v:02x}").yellow())?;
} else {
write!(f, "{v:02x} ")?;
}
offset += 1;
}
let out = A::instruction_to_string(instruction);
let instruction_padding =
(maximum_widths.instruction_bytes - instruction.bytes.len()) * 3;
writeln!(f, "{:instruction_padding$}] {}", "", out.purple())?;
if let Some(annotation) = annotations.peek()
&& annotation.offset_in_section >= instruction_offset
&& annotation.offset_in_section < instruction_end
{
let num_spaces = name_width
+ address_width
+ 7
+ (annotation.offset_in_section - instruction_offset) as usize * 3;
annotation.write(f, &format!("{:num_spaces$}", ""))?;
annotations.next();
}
}
if self.instructions.is_empty() {
write!(
f,
"{name:name_width$} 0x{address:0address_width$x}: [ ",
name = self.name.blue(),
address = self.section_address + self.range.start,
)?;
for i in self.range.clone() {
let byte = self
.section_bytes
.and_then(|bytes| bytes.get(i as usize).copied())
.unwrap_or(0);
write!(f, "{} ", format!("{byte:02x}").yellow())?;
}
writeln!(f, "]")?;
}
for annotation in annotations {
let num_spaces = name_width + address_width + 6;
annotation.write(f, &format!("{:num_spaces$} ", self.name.blue()))?;
}
if let Some(r) = self.reference {
write!(f, "{:name_width$} ", self.name.blue())?;
r.write_to(f)?;
}
writeln!(f)?;
self.write_traces(f, maximum_widths)?;
for message in &self.trace.messages {
writeln!(f, "{:name_width$} {message}", self.name.blue())?;
}
Ok(())
}
fn write_traces(&self, f: &mut String, maximum_widths: &ColumnWidths) -> Result {
let name_width = maximum_widths.name;
let prefix = " TRACE: ";
for trace in &self.trace_messages {
writeln!(f, "{:name_width$}{prefix}{trace}", self.name.blue())?;
}
Ok(())
}
}
impl<A: Arch> Annotation<'_, A> {
fn write(&self, f: &mut String, line_prefix: &str) -> Result {
match &self.kind {
AnnotationKind::MatchedRelaxation(inner) => {
inner.write_to(f, line_prefix)?;
writeln!(f)?;
}
AnnotationKind::Ambiguous(possible) => {
for a in possible {
a.write_to(f, line_prefix)?;
writeln!(f)?;
}
}
AnnotationKind::MatchFailed(failures) => {
for m in failures {
write!(f, "{line_prefix}")?;
m.write_to(f)?;
writeln!(f)?;
}
}
AnnotationKind::Error(error) => {
writeln!(f, "{line_prefix}{}", error.red())?;
}
AnnotationKind::LiteralByteMismatch => {
return Ok(());
}
}
Ok(())
}
}
impl<A: Arch> MatchedRelaxation<A> {
fn write_to(&self, f: &mut String, line_prefix: &str) -> Result {
write!(f, "{line_prefix}")?;
write_carets_for_r_type(f, self.r_type)?;
write!(f, "{} ", self.r_type.to_string().green())?;
if let Some(r) = self.relaxation_kind {
write!(f, "{} ", format!("{r:?}").bright_green())?;
}
Ok(())
}
}
impl<'data, A: Arch> RelaxationMatch<'data, A> {
fn write_to(&self, f: &mut String, line_prefix: &str) -> Result {
let rel = self.relaxation;
write!(f, "{line_prefix}")?;
write_carets_for_r_type(f, rel.new_r_type)?;
write!(f, "{} ", rel.new_r_type.to_string().green())?;
if let Some(alt) = rel.alt_r_type {
write!(f, "/{} ", alt.to_string().green())?;
}
writeln!(
f,
"{} ",
format!("{:?}", rel.relaxation_kind).bright_green()
)?;
Ok(())
}
fn annotation(&self) -> Annotation<'data, A> {
Annotation {
offset_in_section: self.offset,
kind: AnnotationKind::MatchedRelaxation(MatchedRelaxation {
r_type: self.relaxation.new_r_type,
relaxation_kind: Some(self.relaxation.relaxation_kind),
}),
}
}
}
fn write_carets_for_r_type<R: RType>(f: &mut String, r_type: R) -> Result {
let num_carets = num_carets_for_r_type(r_type);
write!(f, "{:^<num_carets$} ", "")?;
Ok(())
}
fn num_carets_for_r_type<R: RType>(r_type: R) -> usize {
let relocation_size = r_type.opt_relocation_info().map_or(1, relocation_num_bytes);
(relocation_size * 3).saturating_sub(1).max(1)
}
impl<A: Arch> FailedMatch<A> {
fn write_to(&self, f: &mut String) -> Result {
write_carets_for_r_type(f, self.candidate.new_r_type)?;
write!(
f,
"{} {:?} {}",
self.candidate.new_r_type.to_string().green(),
self.candidate.relaxation_kind,
self.reason.red()
)?;
Ok(())
}
}
#[derive(Default, PartialEq, Eq, Clone, Copy)]
struct ColumnWidths {
name: usize,
address: usize,
instruction_bytes: usize,
}
impl ColumnWidths {
fn merge(&self, other: ColumnWidths) -> ColumnWidths {
ColumnWidths {
name: self.name.max(other.name),
address: self.address.max(other.address),
instruction_bytes: self.instruction_bytes.max(other.instruction_bytes),
}
}
}
#[derive(Debug)]
struct ResolvedGroup<'data, A: Arch> {
relaxations: Option<RelaxationGroup<'data, A>>,
annotations: Vec<Annotation<'data, A>>,
reference: Reference<'data, A::RType>,
start: u64,
end: u64,
trace: TraceOutput,
}
impl<A: Arch> ResolvedGroup<'_, A> {
fn matches(&self, other: &ResolvedGroup<A>) -> bool {
relaxations_match(self.relaxations.as_ref(), other.relaxations.as_ref())
&& self.reference.matches(other.reference)
}
fn has_error(&self) -> bool {
self.annotations
.iter()
.any(|a| matches!(a.kind, AnnotationKind::Error(_)))
}
}
fn relaxations_match<A: Arch>(
group1: Option<&RelaxationGroup<'_, A>>,
group2: Option<&RelaxationGroup<'_, A>>,
) -> bool {
match (group1, group2) {
(None, None) => true,
(Some(_), None) => false,
(None, Some(_)) => false,
(Some(a), Some(b)) => {
let a_match_results = a.matches_skipping_nops();
let b_match_results = b.matches_skipping_nops();
if a_match_results.len() != b_match_results.len() {
return false;
}
a_match_results
.iter()
.zip(&b_match_results)
.all(|(a, b)| a.matches(b))
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Reference<'data, R: RType> {
referent: Referent<'data, R>,
indirection: Indirection,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
enum Indirection {
#[default]
Direct,
Got,
PltGot,
GotPltGot,
}
impl Indirection {
fn is_via_plt(self) -> bool {
matches!(self, Indirection::PltGot)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Referent<'data, R: RType> {
Unknown,
Named(SymbolName<'data>, i64),
Copy(SymbolName<'data>, i64),
DynamicRelocation(DynamicRelocation<'data, R>),
UnmatchedAddress(UnmatchedAddress),
UnmatchedTlsOffset(i64),
Undefined(UnmatchedAddress),
Absolute(u64),
MergedString(MergedStringRef<'data>),
IFunc(Option<SymbolName<'data>>),
TlsGd(SymtabEntryInfo<'data>),
TlsModuleId,
TlsDescCall,
TlsDesc(SymtabEntryInfo<'data>),
NoRelocation,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct MergedStringRef<'data> {
data: &'data [u8],
named_symbol_addend: Option<i64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
struct UnmatchedAddress {
address: u64,
reason: &'static str,
}
impl<'data, R: RType> Reference<'data, R> {
fn write_to(&self, f: &mut String) -> Result {
match self.indirection {
Indirection::Direct => {}
Indirection::Got => write!(f, "GOT{}", arrow())?,
Indirection::PltGot => write!(f, "PLT{}GOT{}", arrow(), arrow())?,
Indirection::GotPltGot => write!(f, "GOT{}PLT{}GOT{}", arrow(), arrow(), arrow())?,
}
self.referent.write_to(f)?;
Ok(())
}
fn matches(self, other: Reference<'_, R>) -> bool {
self.indirection == other.indirection && self.referent.matches(other.referent)
}
fn verify_consistent_with_r_type(&self, new_r_type: R) -> Result<()> {
let rel_info = new_r_type.relocation_info()?;
match rel_info.kind {
RelocationKind::PltRelative | RelocationKind::PltRelGotBase => {
if !self.indirection.is_via_plt() {
bail!("PLT relocation with non-PLT address");
}
}
_ => {
if self.indirection.is_via_plt() {
bail!("Non-PLT relocation with PLT address");
}
}
}
Ok(())
}
fn unknown() -> Reference<'data, R> {
Reference {
referent: Referent::Unknown,
indirection: Default::default(),
}
}
}
impl<R: RType> Referent<'_, R> {
fn write_to(&self, f: &mut String) -> Result {
match self {
Referent::Unknown => write!(f, "??")?,
Referent::Named(symbol_name, offset) => {
write!(f, "{symbol_name}")?;
if *offset != 0 {
write!(f, " {offset:+}")?;
}
}
Referent::Copy(symbol_name, offset) => {
write!(f, "COPY({symbol_name}")?;
if *offset != 0 {
write!(f, " {offset:+}")?;
}
write!(f, ")")?;
}
Referent::UnmatchedAddress(unmatched) | Referent::Undefined(unmatched) => {
unmatched.write_to(f)?;
}
Referent::UnmatchedTlsOffset(offset) => {
write!(f, "UnmatchedTlsOffset({offset})")?;
}
Referent::Absolute(value) => {
write!(f, "#0x{value:x}")?;
}
Referent::MergedString(merged) => {
merged.write_to(f)?;
}
Referent::DynamicRelocation(dynamic_relocation) => dynamic_relocation.write_to(f)?,
Referent::TlsDesc(symbol) => write!(f, "TlsDesc({symbol})")?,
Referent::IFunc(Some(symbol)) => write!(f, "IFunc({symbol})")?,
Referent::IFunc(None) => write!(f, "UnknownIFunc")?,
Referent::TlsModuleId => write!(f, "TlsModuleId")?,
Referent::TlsGd(symbol) => write!(f, "TlsGd({symbol})")?,
Referent::TlsDescCall => write!(f, "TlsDescCall")?,
Referent::NoRelocation => write!(f, "NoRelocation")?,
}
Ok(())
}
fn matches(self, other: Referent<'_, R>) -> bool {
match (self, other) {
(Referent::Undefined(_), Referent::Undefined(_)) => {
true
}
(Referent::DynamicRelocation(a), Referent::DynamicRelocation(b)) => a.matches(b),
_ => self == other,
}
}
fn is_copy_relocation(&self) -> bool {
matches!(self, Referent::Copy(..))
}
}
impl MergedStringRef<'_> {
fn write_to(&self, f: &mut String) -> Result {
if let Ok(str) = core::str::from_utf8(self.data) {
write!(f, "MergedString({str:?})")?;
} else {
write!(f, "MergedString(InvalidUtf8({:?}))", self.data)?;
}
if let Some(addend) = self.named_symbol_addend {
write!(f, "{addend:+}")?;
}
Ok(())
}
}
impl UnmatchedAddress {
fn write_to(&self, f: &mut String) -> Result {
write!(f, "0x{:x} ({})", self.address, self.reason)?;
Ok(())
}
}
impl<R: RType> DynamicRelocation<'_, R> {
fn matches(self, other: DynamicRelocation<'_, R>) -> bool {
self.normalised() == other.normalised()
}
fn normalised(self) -> Self {
let mut out = self;
if self.r_type.dynamic_relocation_kind() == Some(DynamicRelocationKind::JumpSlot) {
out.r_type = R::from_dynamic_relocation_kind(DynamicRelocationKind::GotEntry);
}
out.entry.is_weak = false;
out
}
}
impl<R: RType> DynamicRelocation<'_, R> {
fn write_to(&self, f: &mut String) -> Result {
write!(
f,
"{}{}{}",
self.r_type.to_string().green().bold(),
arrow(),
self.entry.to_string().cyan()
)?;
if self.addend != 0 {
write!(f, " {:+}", self.addend)?;
}
Ok(())
}
}
fn arrow() -> ColoredString {
"->".bright_yellow()
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub(crate) enum BasicValueKind {
Pointer,
AbsoluteValue,
TlsOffset,
Aarch64TlsOffset,
TlsGd,
TlsModuleId,
TlsDesc,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
enum ValueKind {
Unwrapped(BasicValueKind),
Got(BasicValueKind),
OptionalPlt,
}
#[derive(Clone)]
struct RelaxationTester<'data> {
original_data: &'data [u8],
section_address: u64,
section_bytes: Option<&'data [u8]>,
section_size: u64,
previous_end: u64,
next_modifier: RelocationModifier,
bin: &'data Binary<'data>,
}
impl<'data> RelaxationTester<'data> {
fn new(
original_section: &ElfSection64<'data, '_, LittleEndian>,
bin: &'data Binary<'data>,
section_address: u64,
input_file: §ion_map::InputFile,
) -> Result<Self> {
let section_len = original_section.size();
let section_kind = original_section.kind();
let section_bytes;
match section_kind {
SectionKind::UninitializedData | SectionKind::UninitializedTls | SectionKind::Tls => {
section_bytes = None;
}
_ => {
section_bytes = read_bytes(bin.elf_file, section_address, section_len)?;
if section_bytes.is_none() {
bail!(
"Couldn't read {section_len} bytes for section `{section}` \
from {input_file} at address 0x{section_address:x} \
in `{output_path}`",
output_path = bin.path.display(),
section = original_section.name()?,
);
}
}
}
Ok(RelaxationTester {
original_data: original_section.data()?,
section_bytes,
section_size: original_section.size(),
previous_end: 0,
next_modifier: RelocationModifier::Normal,
bin,
section_address,
})
}
fn match_relaxation<A: Arch>(
&self,
candidate: Relaxation<A>,
rel: &InputRelocation<'data, A>,
) -> Result<RelaxationMatch<'data, A>, FailedMatch<A>> {
let mut offset = rel.offset;
let relaxation_range = A::relaxation_byte_range(candidate);
let base_scratch_offset = relaxation_range.offset_shift;
let copy_start = offset.saturating_sub(base_scratch_offset) as usize;
let copy_end = copy_start + relaxation_range.num_bytes;
let end =
(offset + candidate.relocation_num_bytes().unwrap_or(0) as u64).max(copy_end as u64);
let failure = move |reason: String| {
Err(FailedMatch::new(
candidate,
reason,
offset,
copy_start as u64,
end,
))
};
if offset < self.previous_end {
return failure(format!(
"Relocations out of order or overlap {offset} < {}",
self.previous_end
));
}
if offset < relaxation_range.offset_shift {
return failure("Not enough bytes prior".into());
}
let Some(section_data) = self.section_bytes else {
return failure("Attempted to diff section without data".into());
};
let mut scratch =
vec![0_u8; (A::MAX_RELAX_MODIFY_BEFORE + A::MAX_RELAX_MODIFY_AFTER) as usize];
if copy_end > self.original_data.len() {
return failure("Not enough bytes after".into());
}
let copy_len = copy_end - copy_start;
let scratch = &mut scratch[..copy_len];
scratch.copy_from_slice(&self.original_data[copy_start..copy_end]);
let previous_end = self.previous_end as usize;
if section_data[previous_end..copy_start] != self.original_data[previous_end..copy_start] {
return Err(FailedMatch::new(
candidate,
format!(
"Prior bytes didn't match [0x{:x}..0x{:x})",
self.section_address + previous_end as u64,
self.section_address + copy_start as u64,
),
offset,
previous_end as u64,
end,
));
}
let mut addend = rel.rel.addend();
let mut scratch_offset = base_scratch_offset;
let next_modifier = A::next_relocation_modifier(candidate.relaxation_kind);
A::apply_relaxation(
candidate.relaxation_kind,
scratch,
&mut scratch_offset,
&mut addend,
);
let mask = A::relaxation_mask(candidate, scratch_offset as usize);
if !mask.matches(scratch, §ion_data[copy_start..copy_end]) {
return failure(format!(
"Relaxation output didn't match: {:x?} != {:x?}",
mask.mask_value(scratch),
mask.mask_value(§ion_data[copy_start..copy_end])
));
}
offset = copy_start as u64 + scratch_offset;
let Some(value_bytes) = section_data.get(offset as usize..) else {
return failure("Invalid relocation offset".into());
};
let value = match candidate
.relocation_size()
.and_then(|size| read_value(size, value_bytes))
{
Ok(v) => v,
Err(error) => return failure(error.to_string()),
};
Ok(RelaxationMatch {
relaxation: candidate,
value,
start: copy_start as u64,
end,
original_referent: rel.original_referent(),
addend,
offset,
next_modifier,
})
}
fn read_reference<A: Arch>(
&self,
relaxations_matches: &[RelaxationMatch<A>],
) -> Result<Reference<'data, A::RType>> {
let mut merged_value: u64 = 0;
let mut addend = 0;
let mut referent = None;
let mut all_none = true;
let mut value_kind = ValueKind::OptionalPlt;
let last_match = relaxations_matches.last().unwrap();
for relaxation_match in relaxations_matches {
if let Some(runtime_relocation) = self
.bin
.address_index
.relocation_at_address(self.section_address + relaxation_match.start)
{
let r_type = get_r_type(runtime_relocation);
if let Some(symbol) = self.symtab_entry_for_relocation(runtime_relocation) {
return Ok(Reference {
referent: Referent::DynamicRelocation(DynamicRelocation {
entry: *symbol,
r_type,
addend: runtime_relocation.addend(),
}),
indirection: Indirection::Direct,
});
}
match r_type.dynamic_relocation_kind() {
Some(DynamicRelocationKind::Relative) => {
merged_value =
merged_value.wrapping_add(runtime_relocation.addend() as u64);
continue;
}
Some(DynamicRelocationKind::Irelative) => {
return Ok(Reference {
referent: Referent::IFunc(determine_ifunc_name(
runtime_relocation.addend() as u64,
self.bin,
)),
indirection: Indirection::Direct,
});
}
_ => {
bail!("Unhandled dynamic relocation {r_type}");
}
}
}
let mut value = relaxation_match.value;
if relaxation_match
.relaxation
.new_r_type
.should_ignore_when_computing_referent()
{
value = 0;
}
addend = relaxation_match.addend;
let offset = relaxation_match.offset;
let relocation_info = relaxation_match.relaxation.new_r_type.relocation_info()?;
let relative_to = self.get_relative_to::<A>(offset, relocation_info)?;
if let Some(kind) =
value_kind_for_relocation::<A>(relocation_info.kind, &self.bin.address_index)
{
value_kind = kind;
}
if relocation_info.kind != RelocationKind::None {
all_none = false;
}
match relocation_info.kind {
RelocationKind::TlsDescCall => {
referent = Some(Referent::TlsDescCall);
}
_ => {}
}
if relative_to != 0 && relocation_num_bytes(relocation_info) == 4 {
value = i64::from(value as i32) as u64;
}
value = relative_to.wrapping_add(value);
merged_value = merged_value.wrapping_add(value);
}
if all_none {
referent = Some(Referent::NoRelocation);
}
let last_relocation_info = last_match.relaxation.new_r_type.relocation_info()?;
let mut indirection = Indirection::Direct;
loop {
match value_kind {
ValueKind::OptionalPlt => {
let pointer = merged_value.wrapping_sub(addend as u64);
let got_address = self.bin.address_index.plt_to_got_address::<A>(pointer)?;
let Some(got_address) = got_address else {
value_kind = ValueKind::Unwrapped(BasicValueKind::Pointer);
continue;
};
if indirection == Indirection::Got {
indirection = Indirection::GotPltGot;
} else {
indirection = Indirection::PltGot;
}
if !self.bin.address_index.is_got_address(got_address) {
bail!(
"PLT entry at 0x{pointer:x} points to non-GOT address 0x{got_address:x} in {}",
self.bin
.section_name_containing_address(got_address)
.unwrap_or("??")
);
}
merged_value = got_address;
addend = 0;
value_kind = ValueKind::Got(BasicValueKind::Pointer);
}
ValueKind::Got(inner_kind) => {
merged_value = merged_value.wrapping_sub(addend as u64);
addend = 0;
if !self.bin.address_index.is_got_address(merged_value) {
bail!("Expected GOT address, got 0x{merged_value:x}");
}
if indirection == Indirection::Direct {
indirection = Indirection::Got;
}
let got_entry = self.bin.address_index.dereference_got_address(
merged_value,
last_relocation_info.kind,
self.bin,
inner_kind,
)?;
match got_entry {
Referent::UnmatchedAddress(unmatched) => merged_value = unmatched.address,
Referent::Absolute(absolute_value)
if !self.bin.address_index.is_relocatable() =>
{
merged_value = absolute_value;
}
Referent::UnmatchedTlsOffset(offset) => {
merged_value = offset as u64;
}
other => {
referent = Some(other);
}
}
value_kind = ValueKind::Unwrapped(inner_kind);
}
ValueKind::Unwrapped(_) => break,
}
}
if let Referent::MergedString(orig_merged) = last_match.original_referent {
referent = Some(self.resolve_merged_string::<A>(
merged_value,
last_relocation_info,
orig_merged,
)?);
}
merged_value = merged_value.wrapping_sub(addend as u64);
let referent = referent.unwrap_or_else(|| {
self.resolve_by_symbol_name::<A>(merged_value, last_match.original_referent, value_kind)
});
Ok(Reference {
referent,
indirection,
})
}
fn resolve_by_symbol_name<A: Arch>(
&self,
mut merged_value: u64,
original_referent: Referent<'_, <A as Arch>::RType>,
expected_value_kind: ValueKind,
) -> Referent<'data, <A as Arch>::RType> {
let reason;
if let Referent::Named(original_name, original_addend) = original_referent {
let lookup_result = self.bin.symbol_by_name(original_name.bytes, merged_value);
match &lookup_result {
crate::NameLookupResult::Defined(elf_symbol) => {
let expected_value = match expected_value_kind {
ValueKind::Unwrapped(BasicValueKind::TlsOffset) => {
match self.bin.address_index.bin_attributes.output_kind {
OutputKind::Executable => {
merged_value = i64::from(merged_value as i32) as u64;
elf_symbol.address().wrapping_sub(
self.bin
.address_index
.tls_segment_size
.next_multiple_of(size_of::<u64>() as u64),
)
}
OutputKind::SharedObject => elf_symbol.address(),
}
}
ValueKind::Unwrapped(BasicValueKind::Aarch64TlsOffset) => {
elf_symbol.address() + 2 * 8
}
_ => elf_symbol.address(),
};
let offset = merged_value.wrapping_sub(expected_value) as i64;
if let Ok(mut bytes) = elf_symbol.name_bytes() {
if let Some(at_index) = memchr::memchr(b'@', bytes) {
bytes = &bytes[..at_index];
}
if bytes.is_empty() {
reason = "Symbol has empty name";
} else {
let symbol_name = SymbolName {
bytes,
version: None,
};
if offset.abs() <= 8 + original_addend.abs() {
if has_copy_relocation_for_symbol_named::<A::RType>(
original_name.bytes,
self.bin,
) {
return Referent::Copy(symbol_name, offset);
}
return Referent::Named(symbol_name, offset);
}
reason = "symbol is too far away";
}
} else {
reason = "Error reading symbol name";
}
}
crate::NameLookupResult::Undefined => {
reason = "symbol is undefined";
}
crate::NameLookupResult::Duplicate => {
reason = "symbol has multiple definitions";
}
}
if original_name.bytes.starts_with(b".L")
|| original_name.bytes.is_empty()
|| matches!(lookup_result, crate::NameLookupResult::Undefined)
{
return Referent::Undefined(UnmatchedAddress {
address: merged_value,
reason,
});
}
} else {
reason = "original symbol has no name";
}
Referent::UnmatchedAddress(UnmatchedAddress {
address: merged_value,
reason,
})
}
fn resolve_merged_string<A: Arch>(
&self,
merged_value: u64,
last_relocation_info: RelocationKindInfo,
orig_merged: MergedStringRef<'_>,
) -> Result<Referent<'data, <A as Arch>::RType>> {
let string_address = if let Some(named_symbol_addend) = orig_merged.named_symbol_addend {
(merged_value as i64 - named_symbol_addend) as u64
} else if last_relocation_info.kind == RelocationKind::Relative {
merged_value + A::relocation_to_pc_offset(&last_relocation_info)
} else {
merged_value
};
let bytes = read_bytes_starting_at(self.bin.elf_file, string_address)
.ok()
.flatten()
.with_context(|| format!("Failed to read bytes starting at 0x{string_address:x}"))?;
let null_offset = memchr::memchr(0, bytes).with_context(|| {
format!("Missing null-terminator for merged string starting at 0x{string_address:x}")
})?;
Ok(Referent::MergedString(MergedStringRef {
data: &bytes[..null_offset],
named_symbol_addend: None,
}))
}
fn get_relative_to<A: Arch>(
&self,
offset: u64,
relocation_info: RelocationKindInfo,
) -> Result<u64> {
let mut relative_to = match relocation_info.kind {
RelocationKind::Relative
| RelocationKind::RelativeRiscVLow12
| RelocationKind::RelativeLoongArchHigh
| RelocationKind::PltRelative
| RelocationKind::TlsGd
| RelocationKind::TlsLd
| RelocationKind::TlsDesc
| RelocationKind::TlsDescLoongArch64
| RelocationKind::GotTpOff
| RelocationKind::GotTpOffLoongArch64
| RelocationKind::GotRelative
| RelocationKind::GotRelativeLoongArch64 => self.section_address + offset,
RelocationKind::SymRelGotBase
| RelocationKind::GotRelGotBase
| RelocationKind::TlsGdGotBase
| RelocationKind::GotTpOffGotBase
| RelocationKind::TlsLdGotBase
| RelocationKind::TlsDescGotBase
| RelocationKind::PltRelGotBase => self
.bin
.address_index
.got_base_address
.context("Missing GOT base address")?,
RelocationKind::Absolute
| RelocationKind::AbsoluteSet
| RelocationKind::AbsoluteSetWord6
| RelocationKind::AbsoluteAddition
| RelocationKind::AbsoluteAdditionWord6
| RelocationKind::AbsoluteSubtraction
| RelocationKind::AbsoluteSubtractionWord6
| RelocationKind::Got
| RelocationKind::TlsGdGot
| RelocationKind::GotTpOffGot
| RelocationKind::AbsoluteLowPart
| RelocationKind::TlsDescGot
| RelocationKind::TlsLdGot
| RelocationKind::DtpOff
| RelocationKind::TpOff
| RelocationKind::TlsDescCall
| RelocationKind::PairSubtractionULEB128(..)
| RelocationKind::None
| RelocationKind::Alignment => 0,
};
relative_to &= A::get_relocation_base_mask(&relocation_info);
Ok(relative_to)
}
fn resolve_group_traced<A: Arch>(
&mut self,
section_kind: SectionKind,
group: &RelocationGroup<'data, A>,
) -> ResolvedGroup<'data, A> {
let mut trace = TraceOutput::default();
let mut res = crate::diagnostics::trace_scope(&mut trace, || {
let relaxation_group = self.determine_relaxations(section_kind, group);
self.resolve_group(relaxation_group)
});
res.trace = trace;
res
}
fn determine_relaxations<A: Arch>(
&mut self,
section_kind: SectionKind,
group: &RelocationGroup<'data, A>,
) -> RelaxationGroup<'data, A> {
let match_results = group
.relocations
.iter()
.map(|rel| {
let r_type = get_r_type(&rel.rel);
let mut matched_relaxations = Vec::new();
let mut failed_matches = Vec::new();
A::possible_relaxations_do(r_type, section_kind, |relaxation| {
match self.match_relaxation(relaxation, rel) {
Ok(r) => {
matched_relaxations.push(r);
}
Err(failure) => {
failed_matches.push(failure);
}
};
});
let m = match matched_relaxations.len() {
0 => RelaxationMatchResult::AllFailed(failed_matches),
1 => RelaxationMatchResult::Matched(matched_relaxations.pop().unwrap()),
_ => RelaxationMatchResult::Ambiguous(matched_relaxations),
};
self.accept(&m);
m
})
.collect();
RelaxationGroup {
match_results,
is_complete: group.is_complete_group(),
}
}
fn resolve_group<A: Arch>(
&self,
mut relaxation_group: RelaxationGroup<'data, A>,
) -> ResolvedGroup<'data, A> {
let mut reference = None;
let mut error = None;
if relaxation_group.is_complete
&& let Some(matches) = relaxation_group.matches_if_ok()
&& matches_are_compatible(&matches)
{
match self.read_reference(&matches) {
Ok(r) => reference = Some(r),
Err(e) => error = Some(e),
}
}
if let Some(reference) = reference.as_ref() {
relaxation_group.eliminate_alt_r_types(reference);
}
let mut annotations = relaxation_group
.match_results
.iter()
.flat_map(|r| r.annotations())
.collect_vec();
if let Some(e) = error {
annotations.push(Annotation {
offset_in_section: relaxation_group.end_offset(),
kind: AnnotationKind::Error(e.to_string()),
});
}
ResolvedGroup {
start: relaxation_group.start_offset(),
end: relaxation_group.end_offset(),
relaxations: Some(relaxation_group),
annotations,
reference: reference.unwrap_or(Reference {
referent: Referent::Unknown,
indirection: Default::default(),
}),
trace: TraceOutput::default(),
}
}
fn accept<A: Arch>(&mut self, matched_relaxation: &RelaxationMatchResult<A>) {
self.previous_end = matched_relaxation.end();
self.next_modifier = matched_relaxation.next_modifier();
}
fn is_equal_up_to(&self, offset: u64) -> bool {
(self.section_bytes.is_none() && self.original_data.is_empty())
|| self.section_bytes.is_some_and(|b| {
b[self.previous_end as usize..offset as usize]
== self.original_data[self.previous_end as usize..offset as usize]
})
}
fn symtab_entry_for_relocation(
&self,
runtime_relocation: &object::Relocation,
) -> Option<&SymtabEntryInfo<'data>> {
if let object::RelocationTarget::Symbol(symbol_index) = runtime_relocation.target() {
return self.bin.address_index.dynamic_symbols.get(symbol_index.0);
}
None
}
}
fn matches_are_compatible<A: Arch>(matches: &[RelaxationMatch<'_, A>]) -> bool {
let mut previous_addend = None;
let mut previous_referent = None;
for m in matches {
if previous_addend.is_some_and(|prev| prev != m.addend) {
return false;
}
if previous_referent.is_some_and(|prev| prev != m.original_referent) {
return false;
}
previous_addend = Some(m.addend);
previous_referent = Some(m.original_referent);
}
true
}
fn value_kind_for_relocation<A: Arch>(
relocation_kind: RelocationKind,
address_index: &AddressIndex,
) -> Option<ValueKind> {
let kind = match relocation_kind {
RelocationKind::Absolute
| RelocationKind::AbsoluteLowPart
| RelocationKind::AbsoluteSet
| RelocationKind::AbsoluteSetWord6
| RelocationKind::AbsoluteAddition
| RelocationKind::AbsoluteAdditionWord6
| RelocationKind::AbsoluteSubtraction
| RelocationKind::AbsoluteSubtractionWord6 => {
if address_index.is_relocatable() {
ValueKind::Unwrapped(BasicValueKind::AbsoluteValue)
} else {
return None;
}
}
RelocationKind::Relative
| RelocationKind::RelativeRiscVLow12
| RelocationKind::RelativeLoongArchHigh
| RelocationKind::SymRelGotBase => {
return None;
}
RelocationKind::PltRelative | RelocationKind::PltRelGotBase => ValueKind::OptionalPlt,
RelocationKind::Got
| RelocationKind::GotRelGotBase
| RelocationKind::GotRelative
| RelocationKind::GotRelativeLoongArch64 => ValueKind::Got(BasicValueKind::Pointer),
RelocationKind::DtpOff => ValueKind::Unwrapped(BasicValueKind::TlsOffset),
RelocationKind::TpOff => ValueKind::Unwrapped(A::get_basic_value_for_tp_offset()),
RelocationKind::GotTpOff
| RelocationKind::GotTpOffLoongArch64
| RelocationKind::GotTpOffGot
| RelocationKind::GotTpOffGotBase => ValueKind::Got(BasicValueKind::TlsOffset),
RelocationKind::TlsDesc
| RelocationKind::TlsDescLoongArch64
| RelocationKind::TlsDescGot
| RelocationKind::TlsDescGotBase => {
ValueKind::Got(BasicValueKind::TlsDesc)
}
RelocationKind::TlsLd | RelocationKind::TlsLdGot | RelocationKind::TlsLdGotBase => {
ValueKind::Got(BasicValueKind::TlsModuleId)
}
RelocationKind::TlsGd | RelocationKind::TlsGdGot | RelocationKind::TlsGdGotBase => {
ValueKind::Got(BasicValueKind::TlsGd)
}
RelocationKind::TlsDescCall
| RelocationKind::None
| RelocationKind::PairSubtractionULEB128(..)
| RelocationKind::Alignment => {
return None;
}
};
Some(kind)
}
fn symbol_versions_by_name<'data>(
binaries: &'data [Binary<'data>],
layout: &IndexedLayout<'data>,
) -> HashMap<&'data [u8], SymbolVersions> {
let mut by_name: HashMap<&[u8], SymbolVersions> = layout
.symbol_name_to_section_id
.iter()
.filter_map(|(name, symbol_info)| {
let section = layout.get_elf_section(symbol_info.section_id).ok()?;
if is_merge_section(§ion)
|| section.size() == 0
|| !SUPPORTED_SECTION_KINDS.contains(§ion.kind())
{
None
} else {
let versions = SymbolVersions {
original: *symbol_info,
addresses_by_binary: Vec::with_capacity(binaries.len()),
};
Some((*name, versions))
}
})
.collect();
for (object_index, obj) in binaries.iter().enumerate() {
for sym in obj.elf_file.symbols() {
let Ok(name) = sym.name_bytes() else { continue };
if let hashbrown::hash_map::Entry::Occupied(mut entry) = by_name.entry(name) {
if entry.get().addresses_by_binary.len() == object_index {
entry.get_mut().addresses_by_binary.push(sym.address());
} else {
entry.remove_entry();
}
}
}
}
let num_objects = binaries.len();
by_name.retain(|_, v| v.addresses_by_binary.len() == num_objects);
if let Ok(fn_name) = std::env::var(SHOW_FUNCTION_ENV) {
by_name.retain(|name, _versions| *name == fn_name.as_bytes());
}
by_name
}
fn is_merge_section(section: &ElfSection64<LittleEndian>) -> bool {
section.elf_section_header().sh_flags(LittleEndian) as u32 & object::elf::SHF_MERGE != 0
}
fn unified_sections_from_symbols<'data>(
report: &mut Report,
symbol_versions_by_name: HashMap<&'data [u8], SymbolVersions>,
layout: &IndexedLayout,
binaries: &[Binary],
) -> HashMap<InputSectionId, SectionVersions<'data>> {
let mut matched_sections = HashMap::new();
for (symbol_name, versions) in symbol_versions_by_name {
let unify_result = unify_symbol_section(
&mut matched_sections,
symbol_name,
versions,
binaries,
layout,
);
if let Err(error) = unify_result {
report.add_diff(Diff {
key: format!("error.{}", String::from_utf8_lossy(symbol_name)),
values: DiffValues::PreFormatted(error.to_string()),
});
}
}
matched_sections
}
fn unify_symbol_section<'data>(
matched_sections: &mut HashMap<InputSectionId, SectionVersions<'data>>,
symbol_name: &'data [u8],
mut symbol_versions: SymbolVersions,
binaries: &[Binary],
layout: &IndexedLayout,
) -> Result {
let mut addresses_by_object = core::mem::take(&mut symbol_versions.addresses_by_binary);
if symbol_versions.original.is_ifunc {
return Ok(());
}
for a in &mut addresses_by_object {
*a = a
.checked_sub(symbol_versions.original.offset_in_section)
.context("Underflow when computing section start")?;
}
match matched_sections.entry(symbol_versions.original.section_id) {
hashbrown::hash_map::Entry::Occupied(mut occupied_entry) => {
let existing = occupied_entry.get_mut();
existing.verify_consistent(&addresses_by_object, symbol_name, binaries, layout)?;
if symbol_name < existing.found_via_symbol {
existing.found_via_symbol = symbol_name;
}
}
hashbrown::hash_map::Entry::Vacant(vacant_entry) => {
vacant_entry.insert(SectionVersions {
addresses_by_binary: addresses_by_object,
found_via_symbol: symbol_name,
input_section_id: symbol_versions.original.section_id,
});
}
}
Ok(())
}
struct SymbolVersions {
original: SymbolInfo,
addresses_by_binary: Vec<u64>,
}
#[derive(Clone)]
struct SectionVersions<'data> {
addresses_by_binary: Vec<u64>,
found_via_symbol: &'data [u8],
input_section_id: InputSectionId,
}
impl<'data> SectionVersions<'data> {
fn original_section<'layout>(
&self,
layout: &'layout IndexedLayout<'data>,
) -> Result<ElfSection64<'data, 'layout, LittleEndian>> {
layout.get_elf_section(self.input_section_id)
}
fn verify_consistent(
&self,
addresses_by_binary: &[u64],
symbol_name: &[u8],
binaries: &[Binary<'_>],
layout: &IndexedLayout,
) -> Result {
for (file_number, (&a, &b)) in self
.addresses_by_binary
.iter()
.zip(addresses_by_binary)
.enumerate()
{
if a != b {
bail!(
"Symbols `{existing_sym}` and `{new_sym}` in `{name}` yield \
inconsistent addresses for section `{section_name}` in {input_file}: \
0x{a:x?} vs 0x{b:x?}",
input_file = layout.input_file_for_section(self.input_section_id),
existing_sym = String::from_utf8_lossy(self.found_via_symbol),
new_sym = String::from_utf8_lossy(symbol_name),
section_name = layout.get_elf_section(self.input_section_id)?.name()?,
name = &binaries[file_number],
);
}
}
Ok(())
}
}
pub(crate) fn validate_indexes(bin: &Binary) -> Result {
if let Some(error) = &bin.address_index.index_error {
bail!("{error}");
}
Ok(())
}
pub(crate) fn validate_got_plt(bin: &Binary) -> Result {
let Some(dynamic) = bin.address_index.dynamic_segment_address else {
return Ok(());
};
let got_plt_sec = bin
.section_by_name(GOT_PLT_SECTION_NAME_STR)
.context(".got.plt missing")?;
let got_plt: &[u64] = object::slice_from_all_bytes(got_plt_sec.data()?)
.map_err(|_| anyhow!("Invalid .got.plt"))?;
if got_plt.len() < 3 {
bail!(".got.plt is too short");
}
if got_plt[0] != dynamic {
bail!("First entry of .got.plt should point to .dynamic");
}
if got_plt[1] != 0 || got_plt[2] != 0 {
bail!(".got.plt[1] and .got.plt[2] are reserved and should be zero");
}
Ok(())
}
const ORIG: &str = "ORIG";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct SymtabEntryInfo<'data> {
name: SymbolName<'data>,
is_weak: bool,
visibility: Visibility,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct SymbolName<'data> {
bytes: &'data [u8],
version: Option<&'data [u8]>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Visibility {
Default,
Protected,
Hidden,
Other(u8),
}
#[derive(Clone, Copy)]
enum Data<'data> {
Bytes(&'data [u8]),
Bss,
}
impl std::fmt::Debug for SymbolName<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", String::from_utf8_lossy(self.bytes))?;
if let Some(version) = self.version {
write!(f, "@{}", String::from_utf8_lossy(version))?;
}
Ok(())
}
}
pub(crate) struct AddressIndex<'data> {
plt_indexes: Vec<PltIndex<'data>>,
got_tables: Vec<GotIndex<'data>>,
index_error: Option<anyhow::Error>,
jmprel_address: Option<u64>,
versym_address: Option<u64>,
dynamic_segment_address: Option<u64>,
dynamic_relocations_by_address: HashMap<u64, object::Relocation>,
dynamic_relocations_by_symbol_index: HashMap<object::SymbolIndex, Vec<object::Relocation>>,
tls_segment_size: u64,
jmprel_got_addresses: Vec<u64>,
got_base_address: Option<u64>,
verdef: Vec<Option<&'data [u8]>>,
verneed: Vec<Option<&'data [u8]>>,
dynamic_symbols: Vec<SymtabEntryInfo<'data>>,
bin_attributes: BinAttributes,
symbols_by_address: HashMap<u64, Vec<object::SymbolIndex>>,
}
struct PltIndex<'data> {
plt_base: u64,
entry_length: u64,
bytes: &'data [u8],
}
impl PltIndex<'_> {
fn lookup_got_address<A: Arch>(
&self,
plt_address: u64,
index: &AddressIndex,
) -> Result<Option<u64>> {
if !(self.plt_base..self.plt_base + self.bytes.len() as u64).contains(&plt_address) {
return Ok(None);
}
let offset = plt_address - self.plt_base;
if self.entry_length != 0 && !offset.is_multiple_of(self.entry_length) {
bail!(
"PLT address 0x{plt_address:x} is not aligned to 0x{:x}",
self.entry_length
);
}
let plt_entry = if self.entry_length == 0 {
self.decode_plt_entry_with_size::<A>(offset, 8)
.or_else(|| self.decode_plt_entry_with_size::<A>(offset, 16))
} else {
self.decode_plt_entry_with_size::<A>(offset, self.entry_length)
}
.with_context(|| format!("Unrecognised PLT entry format at 0x{plt_address:x}"))?;
let got_address = match plt_entry {
PltEntry::DerefJmp(address) => address,
PltEntry::JumpSlot(slot_index) => index
.jmprel_got_addresses
.get(slot_index as usize)
.copied()
.with_context(|| {
format!(
"Invalid jump slot index {slot_index} out of {}",
index.jmprel_got_addresses.len()
)
})?,
};
Ok(Some(got_address))
}
fn decode_plt_entry_with_size<A: Arch>(
&self,
offset: u64,
entry_size: u64,
) -> Option<PltEntry> {
let entry_bytes = &self.bytes[offset as usize..(offset + entry_size) as usize];
A::decode_plt_entry(entry_bytes, self.plt_base, offset)
}
}
impl<'data> AddressIndex<'data> {
pub(crate) fn new(elf_file: &'data ElfFile64<'data>) -> Self {
let mut info = Self {
index_error: None,
jmprel_address: None,
versym_address: None,
dynamic_segment_address: None,
got_base_address: None,
tls_segment_size: get_tls_segment_size(elf_file),
plt_indexes: Default::default(),
got_tables: Default::default(),
verdef: Default::default(),
verneed: Default::default(),
dynamic_symbols: Default::default(),
jmprel_got_addresses: Vec::new(),
dynamic_relocations_by_address: Default::default(),
bin_attributes: BinAttributes {
output_kind: if elf_file.kind() == ObjectKind::Executable {
OutputKind::Executable
} else {
OutputKind::SharedObject
},
relocatability: Relocatability::NonRelocatable,
link_type: LinkType::Static,
},
dynamic_relocations_by_symbol_index: Default::default(),
symbols_by_address: index_symbols_by_address(elf_file),
};
if let Err(error) = info.build_indexes(elf_file) {
info.index_error = Some(error);
}
info
}
fn build_indexes(&mut self, elf_file: &ElfFile64<'data>) -> Result {
self.index_dynamic(elf_file);
self.verdef = Self::index_verdef(elf_file)?;
self.verneed = Self::index_verneed(elf_file)?;
self.dynamic_symbols = self.index_dynamic_symbols(elf_file)?;
self.index_got_tables(elf_file).unwrap();
self.index_relocations(elf_file);
self.index_plt_sections(elf_file)?;
Ok(())
}
fn index_verdef(elf_file: &ElfFile64<'data>) -> Result<Vec<Option<&'data [u8]>>> {
let e = LittleEndian;
let mut versions = Vec::new();
let maybe_verdef = elf_file
.sections()
.find_map(|section| {
section
.elf_section_header()
.gnu_verdef(e, elf_file.data())
.transpose()
})
.transpose()?;
let Some((mut verdef_iterator, strings_index)) = maybe_verdef else {
return Ok(versions);
};
let strings = elf_file
.elf_section_table()
.strings(e, elf_file.data(), strings_index)?;
while let Some((_verdef, mut aux_iterator)) = verdef_iterator.next()? {
if let Some(aux) = aux_iterator.next()? {
let name = aux.name(e, strings)?;
versions.push(Some(name));
}
}
Ok(versions)
}
fn index_verneed(elf_file: &ElfFile64<'data>) -> Result<Vec<Option<&'data [u8]>>> {
let e = LittleEndian;
let mut versions = Vec::new();
let maybe_verneed = elf_file
.sections()
.find_map(|section| {
section
.elf_section_header()
.gnu_verneed(e, elf_file.data())
.transpose()
})
.transpose()?;
let Some((mut verneed_iterator, strings_index)) = maybe_verneed else {
return Ok(versions);
};
let strings = elf_file
.elf_section_table()
.strings(e, elf_file.data(), strings_index)?;
while let Some((_verneed, mut aux_iterator)) = verneed_iterator.next()? {
while let Some(aux) = aux_iterator.next()? {
let name = aux.name(e, strings)?;
let index = aux.vna_other.get(e) as usize;
if index >= versions.len() {
versions.resize(index + 1, None);
}
versions[index] = Some(name);
}
}
Ok(versions)
}
fn index_dynamic_symbols(
&self,
elf_file: &ElfFile64<'data>,
) -> Result<Vec<SymtabEntryInfo<'data>>> {
let symbol_version_indexes: Option<&[u16]> = self
.versym_address
.and_then(|address| {
elf_file
.sections()
.find(|section| section.address() == address)
})
.and_then(|section| section.data().ok())
.and_then(|data| object::slice_from_all_bytes(data).ok());
let mut dynamic_symbol_names = Vec::new();
let mut max_index = 0;
for sym in elf_file.dynamic_symbols() {
let sym_index = sym.index().0;
max_index = max_index.max(sym_index);
let version_index = symbol_version_indexes
.and_then(|indexes| indexes.get(sym_index))
.copied();
let version: Option<&[u8]> = match version_index {
Some(object::elf::VER_NDX_LOCAL) | Some(object::elf::VER_NDX_GLOBAL)
if !sym.is_definition() =>
{
Some(b"*unversioned*")
}
Some(object::elf::VER_NDX_LOCAL) => Some(b"*local*"),
Some(object::elf::VER_NDX_GLOBAL) => Some(b"*global*"),
Some(version_index) if version_index > object::elf::VER_NDX_GLOBAL => self
.verdef
.get(version_index as usize - 1)
.or_else(|| self.verneed.get(version_index as usize))
.copied()
.flatten(),
_ => None,
};
let name_bytes = sym.name_bytes()?;
let name = SymbolName {
bytes: name_bytes,
version,
};
let visibility = Visibility::from_sym(sym.elf_symbol());
ensure!(
visibility != Visibility::Hidden,
"Dynamic symbol {name} has unexpected hidden visibility"
);
while dynamic_symbol_names.len() < sym_index {
dynamic_symbol_names.push(SymtabEntryInfo {
name: SymbolName {
bytes: &[],
version: None,
},
is_weak: false,
visibility: Visibility::Default,
});
}
dynamic_symbol_names.push(SymtabEntryInfo {
name,
is_weak: sym.is_weak(),
visibility,
});
}
if let Some(versym) = symbol_version_indexes {
let versym_len = versym.len();
let num_symbols = max_index + 1;
if versym_len != num_symbols {
bail!(".gnu.version contains {versym_len}, but .dynsym contains {num_symbols}");
}
}
Ok(dynamic_symbol_names)
}
fn index_relocations(&mut self, elf_file: &ElfFile64<'data>) {
if let Some(dynamic_relocations) = elf_file.dynamic_relocations() {
for (_, rel) in dynamic_relocations {
if let RelocationTarget::Symbol(symbol_index) = rel.target() {
self.dynamic_relocations_by_symbol_index
.entry(symbol_index)
.or_default()
.push(rel);
}
}
}
if let Some(dynamic_relocations) = elf_file.dynamic_relocations() {
self.dynamic_relocations_by_address
.extend(dynamic_relocations);
}
for section in elf_file.sections() {
self.dynamic_relocations_by_address
.extend(section.relocations());
}
}
fn index_plt_sections(&mut self, elf_file: &ElfFile64<'data>) -> Result {
self.index_plt_named(elf_file, PLT_SECTION_NAME_STR)?;
self.index_plt_named(elf_file, PLT_SEC_SECTION_NAME_STR)?;
self.index_plt_named(elf_file, PLT_GOT_SECTION_NAME_STR)?;
self.index_plt_named(elf_file, IPLT_SECTION_NAME_STR)?;
Ok(())
}
fn index_plt_named(&mut self, elf_file: &ElfFile64<'data>, section_name: &str) -> Result {
let Some(section) = elf_file.section_by_name(section_name) else {
return Ok(());
};
let Ok(bytes) = section.data() else {
return Ok(());
};
let entry_length = section.elf_section_header().sh_entsize(LittleEndian) as usize;
if ![0, 8, 0x10].contains(&entry_length) {
bail!("{section_name} has unrecognised entry length {entry_length}");
}
let plt_base = section.address();
self.plt_indexes.push(PltIndex {
bytes,
plt_base,
entry_length: entry_length as u64,
});
Ok(())
}
fn index_got_tables(&mut self, elf_file: &ElfFile64<'data>) -> Result {
self.got_tables = [GOT_PLT_SECTION_NAME_STR, GOT_SECTION_NAME_STR]
.iter()
.filter_map(|table_name| Self::index_got_table(elf_file, table_name).transpose())
.try_collect()?;
self.got_base_address = self.got_tables.first().map(|t| t.address_range.start);
Ok(())
}
fn index_got_table(
elf_file: &ElfFile64<'data>,
table_name: &str,
) -> Result<Option<GotIndex<'data>>> {
let Some(got_section) = elf_file.section_by_name(table_name) else {
return Ok(None);
};
let data = got_section.data()?;
let raw_entries: &[u64] = if data.is_empty() {
&[]
} else {
let entry_size = size_of::<u64>();
object::slice_from_bytes(data, data.len() / entry_size)
.unwrap()
.0
};
let base = got_section.address();
Ok(Some(GotIndex {
address_range: base..base + data.len() as u64,
entries: raw_entries,
}))
}
fn index_dynamic(&mut self, elf_file: &ElfFile64) {
let e = LittleEndian;
let dynamic_segment = elf_file
.elf_program_headers()
.iter()
.find(|seg| seg.p_type(LittleEndian) == object::elf::PT_DYNAMIC);
self.dynamic_segment_address = dynamic_segment.map(|seg| seg.p_vaddr(e));
if elf_file.elf_header().e_type(LittleEndian) == object::elf::ET_DYN {
self.bin_attributes.relocatability = Relocatability::Relocatable;
}
if dynamic_segment.is_none() {
self.bin_attributes.output_kind = OutputKind::Executable;
};
dynamic_segment
.and_then(|seg| seg.data(LittleEndian, elf_file.data()).ok())
.and_then(|dynamic_table_data| {
object::slice_from_all_bytes::<object::elf::Dyn64<LittleEndian>>(dynamic_table_data)
.ok()
})
.unwrap_or_default()
.iter()
.for_each(|entry| match entry.d_tag.get(e) as u32 {
object::elf::DT_JMPREL => {
self.jmprel_address = Some(entry.d_val.get(e));
}
object::elf::DT_VERSYM => {
self.versym_address = Some(entry.d_val.get(e));
}
object::elf::DT_FLAGS_1 => {
if entry.d_val.get(e) & u64::from(object::elf::DF_1_PIE) != 0 {
self.bin_attributes.output_kind = OutputKind::Executable;
}
}
object::elf::DT_NEEDED => {
self.bin_attributes.link_type = LinkType::Dynamic;
}
_ => {}
});
}
fn plt_to_got_address<A: Arch>(&self, plt_address: u64) -> Result<Option<u64>> {
self.plt_indexes
.iter()
.find_map(|index| index.lookup_got_address::<A>(plt_address, self).transpose())
.transpose()
}
fn is_got_address(&self, address: u64) -> bool {
self.got_tables
.iter()
.any(|t| t.address_range.contains(&address))
}
pub(crate) fn symbols_at_address(&self, address: u64) -> &[object::SymbolIndex] {
self.symbols_by_address
.get(&address)
.map(|s| s.as_slice())
.unwrap_or_default()
}
pub(crate) fn relocation_at_address(&self, address: u64) -> Option<&object::Relocation> {
self.dynamic_relocations_by_address.get(&address)
}
fn dereference_got_address<R: RType>(
&self,
got_address: u64,
relocation_kind: RelocationKind,
bin: &Binary<'data>,
expected_value_kind: BasicValueKind,
) -> Result<Referent<'data, R>> {
let table = self
.got_tables
.iter()
.find(|table| table.address_range.contains(&got_address))
.context("Address isn't in any GOT tables")?;
table.dereference_got_address(got_address, relocation_kind, bin, expected_value_kind)
}
fn is_relocatable(&self) -> bool {
self.bin_attributes.relocatability == Relocatability::Relocatable
}
}
fn get_tls_segment_size(elf_file: &ElfFile64) -> u64 {
elf_file
.elf_program_headers()
.iter()
.find_map(|header| {
(header.p_type(LittleEndian) == object::elf::PT_TLS)
.then(|| header.p_memsz(LittleEndian))
})
.unwrap_or(0)
}
fn index_symbols_by_address(elf_file: &ElfFile64) -> HashMap<u64, Vec<object::SymbolIndex>> {
let mut out: HashMap<u64, Vec<object::SymbolIndex>> = HashMap::new();
for sym in elf_file.symbols() {
out.entry(sym.address()).or_default().push(sym.index());
}
out
}
struct GotIndex<'data> {
address_range: Range<u64>,
entries: &'data [u64],
}
impl<'data> GotIndex<'data> {
fn dereference_got_address<R: RType>(
&self,
got_address: u64,
relocation_kind: RelocationKind,
bin: &Binary<'data>,
expected_value_kind: BasicValueKind,
) -> Result<Referent<'data, R>> {
let index = &bin.address_index;
let offset = got_address
.checked_sub(self.address_range.start)
.context("got_address outside index range")?;
let entry_size = size_of::<u64>() as u64;
if !offset.is_multiple_of(entry_size) {
bail!("Unaligned reference to GOT 0x{got_address:x}");
}
if let Some(rel) = index.dynamic_relocations_by_address.get(&got_address) {
let r_type = get_r_type::<R>(rel);
let dynamic_relocation_kind = r_type
.dynamic_relocation_kind()
.with_context(|| format!("Unsupported dynamic relocation {r_type}"))?;
let symbol = if let object::RelocationTarget::Symbol(symbol_index) = rel.target() {
Some(
index
.dynamic_symbols
.get(symbol_index.0)
.context("Symbol index out of range")?,
)
} else {
None
};
match dynamic_relocation_kind {
DynamicRelocationKind::Relative => {
Ok(Referent::UnmatchedAddress(UnmatchedAddress {
address: rel.addend() as u64,
..Default::default()
}))
}
DynamicRelocationKind::Irelative => Ok(Referent::IFunc(determine_ifunc_name(
rel.addend() as u64,
bin,
))),
DynamicRelocationKind::DtpMod => {
match (expected_value_kind, symbol) {
(BasicValueKind::TlsModuleId, None) => Ok(Referent::TlsModuleId),
(BasicValueKind::TlsModuleId, Some(symbol)) => {
bail!("Expected TLSLD, but found DTPMOD with symbol (`{symbol}`)");
}
(BasicValueKind::TlsGd, Some(symbol)) => Ok(Referent::TlsGd(*symbol)),
(BasicValueKind::TlsGd, None) => {
let tls_offset =
read_word_at(bin.elf_file, got_address + size_of::<u64>() as u64)?
.context("Short read after DTPMOD")?
as i64;
Ok(Referent::UnmatchedTlsOffset(tls_offset))
}
(other, _) => bail!("Unexpected DTPMOD when looking for {other:?}"),
}
}
DynamicRelocationKind::TpOff if symbol.is_none() => {
Ok(Referent::UnmatchedTlsOffset(rel.addend()))
}
DynamicRelocationKind::TlsDesc => {
if let Some(symbol) = symbol {
Ok(Referent::TlsDesc(*symbol))
} else {
Ok(Referent::UnmatchedTlsOffset(rel.addend()))
}
}
_ => {
let symbol = symbol.with_context(|| format!("{r_type} without symbol"))?;
Ok(Referent::DynamicRelocation(DynamicRelocation {
entry: *symbol,
r_type,
addend: rel.addend(),
}))
}
}
} else {
let raw_value = *self
.entries
.get((offset / entry_size) as usize)
.context("got_address past end of index range")?;
match relocation_kind {
RelocationKind::GotTpOff
| RelocationKind::GotTpOffLoongArch64
| RelocationKind::GotTpOffGot
| RelocationKind::GotTpOffGotBase => {
Ok(Referent::UnmatchedTlsOffset(raw_value as i64))
}
RelocationKind::TlsDescCall => Ok(Referent::TlsDescCall),
RelocationKind::Absolute
| RelocationKind::AbsoluteLowPart
| RelocationKind::AbsoluteSet
| RelocationKind::AbsoluteSetWord6
| RelocationKind::AbsoluteAddition
| RelocationKind::AbsoluteAdditionWord6
| RelocationKind::AbsoluteSubtraction
| RelocationKind::AbsoluteSubtractionWord6
| RelocationKind::Relative
| RelocationKind::RelativeRiscVLow12
| RelocationKind::RelativeLoongArchHigh
| RelocationKind::SymRelGotBase
| RelocationKind::GotRelGotBase
| RelocationKind::Got
| RelocationKind::PltRelGotBase
| RelocationKind::PltRelative
| RelocationKind::GotRelative
| RelocationKind::GotRelativeLoongArch64
| RelocationKind::None
| RelocationKind::PairSubtractionULEB128(..)
| RelocationKind::Alignment => Ok(Referent::Absolute(raw_value)),
RelocationKind::TlsGd
| RelocationKind::TlsGdGot
| RelocationKind::TlsGdGotBase
| RelocationKind::TlsLd
| RelocationKind::TlsLdGot
| RelocationKind::TlsLdGotBase
| RelocationKind::DtpOff
| RelocationKind::TpOff
| RelocationKind::TlsDesc
| RelocationKind::TlsDescLoongArch64
| RelocationKind::TlsDescGot
| RelocationKind::TlsDescGotBase => {
bail!("Missing dynamic relocation for {relocation_kind:?}")
}
}
}
}
}
fn determine_ifunc_name<'data>(address: u64, bin: &Binary<'data>) -> Option<SymbolName<'data>> {
bin.address_index
.symbols_at_address(address)
.iter()
.filter_map(|symbol_index| {
let symbol = bin.elf_file.symbol_by_index(*symbol_index).ok()?;
if symbol.elf_symbol().st_type() != object::elf::STT_FUNC {
return None;
}
Some(SymbolName {
bytes: symbol.name_bytes().ok()?,
version: None,
})
})
.max()
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct DynamicRelocation<'data, R: RType> {
entry: SymtabEntryInfo<'data>,
r_type: R,
addend: i64,
}
fn read_segment<'data>(elf_file: &ElfFile64<'data>, address: u64) -> Result<Option<Data<'data>>> {
for (seg_index, raw_seg) in elf_file.elf_program_headers().iter().enumerate() {
let e = LittleEndian;
if raw_seg.p_type(e) != object::elf::PT_LOAD {
continue;
}
let seg_address = raw_seg.p_paddr(e);
let seg_len = raw_seg.p_memsz(e);
let seg_end = seg_address + seg_len;
if seg_address <= address && address < seg_end {
let start = (address - seg_address) as usize;
let file_start = raw_seg.p_offset(e) as usize;
let file_size = raw_seg.p_filesz(e) as usize;
let file_end = file_start + file_size;
let file_bytes = elf_file.data();
if file_bytes.is_empty() {
return Ok(Some(Data::Bss));
}
let bytes = &file_bytes
.get(file_start + start..file_end)
.with_context(|| format!("Invalid ELF segment {seg_index}"))?;
return Ok(Some(Data::Bytes(bytes)));
}
}
Ok(None)
}
fn read_word_at(elf_file: &ElfFile64, address: u64) -> Result<Option<u64>> {
let Some(bytes) = read_bytes(elf_file, address, size_of::<u64>() as u64)? else {
return Ok(None);
};
let Some(chunk) = bytes.first_chunk() else {
return Ok(None);
};
Ok(Some(u64::from_le_bytes(*chunk)))
}
fn read_bytes<'data>(
elf_file: &ElfFile64<'data>,
address: u64,
len: u64,
) -> Result<Option<&'data [u8]>> {
Ok(
read_segment(elf_file, address)?.and_then(|data| match data {
Data::Bytes(bytes) => bytes.get(..len as usize),
Data::Bss => None,
}),
)
}
fn read_bytes_starting_at<'data>(
elf_file: &ElfFile64<'data>,
address: u64,
) -> Result<Option<&'data [u8]>> {
Ok(
read_segment(elf_file, address)?.and_then(|data| match data {
Data::Bytes(bytes) => Some(bytes),
Data::Bss => None,
}),
)
}
impl Display for SymbolName<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
symbolic_demangle::demangle(&String::from_utf8_lossy(self.bytes))
)?;
if let Some(version) = self.version {
write!(f, "@{}", String::from_utf8_lossy(version))?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
struct BinAttributes {
output_kind: OutputKind,
relocatability: Relocatability,
link_type: LinkType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum OutputKind {
Executable,
SharedObject,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Relocatability {
Relocatable,
NonRelocatable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum LinkType {
Dynamic,
Static,
}
impl BinAttributes {
fn type_name(self) -> &'static str {
match (self.output_kind, self.relocatability, self.link_type) {
(OutputKind::Executable, Relocatability::Relocatable, LinkType::Dynamic) => {
"dynamic-pie"
}
(OutputKind::Executable, Relocatability::Relocatable, LinkType::Static) => "static-pie",
(OutputKind::Executable, Relocatability::NonRelocatable, LinkType::Dynamic) => {
"dynamic-non-pie"
}
(OutputKind::Executable, Relocatability::NonRelocatable, LinkType::Static) => {
"static-non-pie"
}
(OutputKind::SharedObject, Relocatability::Relocatable, LinkType::Dynamic) => {
"shared-object"
}
(OutputKind::SharedObject, _, _) => "invalid-shared-object",
}
}
}
fn relocation_num_bytes(info: RelocationKindInfo) -> usize {
match info.size {
linker_utils::elf::RelocationSize::ByteSize(b) => b,
linker_utils::elf::RelocationSize::BitMasking(mask) => {
(mask.range.end.div_ceil(8) - mask.range.start / 8) as usize
}
}
}
fn read_value(size: RelocationSize, value_bytes: &[u8]) -> Result<u64> {
match size {
RelocationSize::ByteSize(8) => Ok(u64::from_le_bytes(
*value_bytes
.first_chunk::<8>()
.context("Invalid relocation offset")?,
)),
RelocationSize::ByteSize(4) => Ok(u64::from(u32_from_slice(value_bytes))),
RelocationSize::ByteSize(0) => Ok(0),
RelocationSize::ByteSize(other) => bail!("Unsupported relocation size {other}"),
RelocationSize::BitMasking(BitMask {
range,
instruction: insn,
}) => {
let (raw_value, _negative) = insn.read_value(value_bytes);
Ok(raw_value << range.start)
}
}
}
impl<A: Arch> Relaxation<A> {
fn relocation_size(&self) -> Result<RelocationSize> {
let size = self.new_r_type.relocation_info()?.size;
if let Some(alt_r_type) = self.alt_r_type {
let alt_size = alt_r_type.relocation_info()?.size;
assert_eq!(alt_size, size);
}
Ok(size)
}
fn relocation_num_bytes(&self) -> Result<usize> {
let relocation_info = self.new_r_type.relocation_info()?;
Ok(relocation_num_bytes(relocation_info))
}
}
fn has_copy_relocation_for_symbol_named<R: RType>(symbol_name: &[u8], bin: &Binary) -> bool {
bin.name_index
.dynamic_by_name
.get(symbol_name)
.is_some_and(|symbol_indexes| {
symbol_indexes.iter().any(|symbol_index| {
bin.address_index
.dynamic_relocations_by_symbol_index
.get(symbol_index)
.is_some_and(|relocations| {
relocations.iter().any(|rel| {
let r_type = get_r_type::<R>(rel);
r_type.dynamic_relocation_kind() == Some(DynamicRelocationKind::Copy)
})
})
})
})
}
fn populate_section_coverage(cov: &mut crate::Coverage, layout: &IndexedLayout<'_>) {
layout.all_sections_do(|section_info| {
let Ok(elf_section) = layout.get_elf_section(section_info.section_id) else {
return;
};
let Some(name) = elf_section.name_bytes().ok() else {
return;
};
cov.sections.insert(
section_info.section_id,
SectionCoverage {
original_file: layout
.input_file_for_section(section_info.section_id)
.identifier
.to_owned(),
name: String::from_utf8_lossy(name).into_owned(),
num_bytes: elf_section.size(),
diffed: false,
},
);
});
}
impl Display for SymtabEntryInfo<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.name, f)?;
if self.is_weak {
write!(f, " (weak)")?;
}
match self.visibility {
Visibility::Default => {}
Visibility::Protected => write!(f, " (protected)")?,
Visibility::Hidden => write!(f, " (hidden)")?,
Visibility::Other(other) => write!(f, " (vis={other})")?,
}
Ok(())
}
}
impl Display for OutputKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OutputKind::Executable => write!(f, "executable"),
OutputKind::SharedObject => write!(f, "shared-object"),
}
}
}
impl Visibility {
fn from_sym(elf_symbol: &object::elf::Sym64<LittleEndian>) -> Visibility {
match elf_symbol.st_visibility() {
object::elf::STV_DEFAULT => Visibility::Default,
object::elf::STV_PROTECTED => Visibility::Protected,
object::elf::STV_HIDDEN => Visibility::Hidden,
other => Visibility::Other(other),
}
}
}