Skip to content

Commit 4449ff1

Browse files
committed
PPC: Handle following bctr jump table control flow
1 parent 2132946 commit 4449ff1

File tree

1 file changed

+47
-4
lines changed

1 file changed

+47
-4
lines changed

objdiff-core/src/arch/ppc.rs

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -533,9 +533,13 @@ fn make_fake_pool_reloc(offset: i16, cur_addr: u32, pool_reloc: &ObjReloc) -> Op
533533
// to the queue. Conditional branches will traverse both the path where the branch is taken and the
534534
// one where it's not. Unconditional branches only follow the branch, ignoring any code immediately
535535
// after the branch instruction.
536-
// Limitations: This method cannot follow jump tables. This is because the jump table is located in
537-
// the .data section, but ObjArch.process_code only has access to the .text section. This means that
538-
// it will miss most of the cases in a switch statement that uses a jump table.
536+
// Limitations: This method cannot read jump tables. This is because the jump tables are located in
537+
// the .data section, but ObjArch.process_code only has access to the .text section. In order to
538+
// work around this limitation and avoid completely missing most code inside switch statements that
539+
// use jump tables, we instead guess that any parts of a function we missed were switch cases, and
540+
// traverse them as if the last `bctr` before that address had branched there. This should be fairly
541+
// accurate in practice - in testing the only instructions it seems to miss are double branches that
542+
// the compiler generates in error which can never be reached during normal execution anyway.
539543
fn generate_fake_pool_reloc_for_addr_mapping(
540544
func_address: u64,
541545
code: &[u8],
@@ -545,6 +549,7 @@ fn generate_fake_pool_reloc_for_addr_mapping(
545549
let mut pool_reloc_for_addr = HashMap::new();
546550
let mut ins_iters_with_gpr_state =
547551
vec![(InsIter::new(code, func_address as u32), HashMap::new())];
552+
let mut gpr_state_at_bctr = BTreeMap::new();
548553
while let Some((ins_iter, mut gpr_pool_relocs)) = ins_iters_with_gpr_state.pop() {
549554
for (cur_addr, ins) in ins_iter {
550555
if visited_ins_addrs.contains(&cur_addr) {
@@ -554,8 +559,8 @@ fn generate_fake_pool_reloc_for_addr_mapping(
554559
visited_ins_addrs.insert(cur_addr);
555560

556561
let simplified = ins.simplified();
557-
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
558562

563+
// First handle traversing the function's control flow.
559564
let mut branch_dest = None;
560565
for arg in simplified.args_iter() {
561566
if let Argument::BranchDest(dest) = arg {
@@ -596,7 +601,19 @@ fn generate_fake_pool_reloc_for_addr_mapping(
596601
}
597602
}
598603
}
604+
match ins.op {
605+
Opcode::Bcctr => {
606+
if simplified.mnemonic == "bctr" {
607+
// Unconditional branch to count register.
608+
// Likely a jump table.
609+
gpr_state_at_bctr.insert(cur_addr, gpr_pool_relocs.clone());
610+
}
611+
}
612+
_ => {}
613+
}
599614

615+
// Then handle keeping track of which GPR contains which pool relocation.
616+
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
600617
if let Some(reloc) = reloc {
601618
// This instruction has a real relocation, so it may be a pool load we want to keep
602619
// track of.
@@ -667,6 +684,32 @@ fn generate_fake_pool_reloc_for_addr_mapping(
667684
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
668685
}
669686
}
687+
688+
// Finally, if we're about to finish the outer loop and don't have any more control flow to
689+
// follow, we check if there are any instruction addresses in this function that we missed.
690+
// If so, and if there were any `bctr` instructions before those points in this function,
691+
// then we try to traverse those missing spots as switch cases.
692+
if ins_iters_with_gpr_state.is_empty() {
693+
let unseen_addrs = (func_address as u32..func_address as u32 + code.len() as u32)
694+
.step_by(4)
695+
.filter(|addr| !visited_ins_addrs.contains(&addr));
696+
for unseen_addr in unseen_addrs {
697+
let prev_bctr_gpr_state = gpr_state_at_bctr
698+
.iter()
699+
.filter(|(&addr, _)| addr < unseen_addr)
700+
.min_by_key(|(&addr, _)| addr)
701+
.and_then(|(_, gpr_state)| Some(gpr_state));
702+
if let Some(gpr_pool_relocs) = prev_bctr_gpr_state {
703+
let dest_offset_into_func = unseen_addr - func_address as u32;
704+
let dest_code_slice = &code[dest_offset_into_func as usize..];
705+
ins_iters_with_gpr_state.push((
706+
InsIter::new(dest_code_slice, unseen_addr),
707+
gpr_pool_relocs.clone(),
708+
));
709+
break;
710+
}
711+
}
712+
}
670713
}
671714

672715
pool_reloc_for_addr

0 commit comments

Comments
 (0)