Skip to content

Commit 2132946

Browse files
committed
PPC: Follow branches to improve pool detection accuracy
1 parent 0c7df23 commit 2132946

File tree

1 file changed

+128
-72
lines changed

1 file changed

+128
-72
lines changed

objdiff-core/src/arch/ppc.rs

Lines changed: 128 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{
22
borrow::Cow,
3-
collections::{BTreeMap, HashMap},
3+
collections::{BTreeMap, HashMap, HashSet},
44
};
55

66
use anyhow::{bail, ensure, Result};
@@ -458,12 +458,12 @@ fn get_offset_and_addr_gpr_for_possible_pool_reference(
458458

459459
// Remove the relocation we're keeping track of in a particular register when an instruction reuses
460460
// that register to hold some other value, unrelated to pool relocation addresses.
461-
fn clear_overwritten_gprs(ins: Ins, active_pool_relocs: &mut HashMap<u8, ObjReloc>) {
461+
fn clear_overwritten_gprs(ins: Ins, gpr_pool_relocs: &mut HashMap<u8, ObjReloc>) {
462462
let mut def_args = Arguments::default();
463463
ins.parse_defs(&mut def_args);
464464
for arg in def_args {
465465
if let Argument::GPR(gpr) = arg {
466-
active_pool_relocs.remove(&gpr.0);
466+
gpr_pool_relocs.remove(&gpr.0);
467467
}
468468
}
469469
}
@@ -526,90 +526,146 @@ fn make_fake_pool_reloc(offset: i16, cur_addr: u32, pool_reloc: &ObjReloc) -> Op
526526
// of pooled data relocations in them, finding which instructions load data from those addresses,
527527
// and constructing a mapping of the address of that instruction to a "fake pool relocation" that
528528
// simulates what that instruction's relocation would look like if data hadn't been pooled.
529-
// Limitations: This method currently only goes through the instructions in a function in linear
530-
// order, from start to finish. It does *not* follow any branches. This means that it could have
531-
// false positives or false negatives in determining which relocation is currently loaded in which
532-
// register at any given point in the function, as control flow is not respected.
533-
// There are currently no known examples of this method producing inaccurate results in reality, but
534-
// if examples are found, it may be possible to update this method to also follow all branches so
535-
// that it produces more accurate results.
529+
// This method tries to follow the function's proper control flow. It keeps track of a queue of
530+
// states it hasn't traversed yet, where each state holds an instruction address and a HashMap of
531+
// which registers hold which pool relocations at that point.
532+
// When a conditional or unconditional branch is encountered, the destination of the branch is added
533+
// to the queue. Conditional branches will traverse both the path where the branch is taken and the
534+
// one where it's not. Unconditional branches only follow the branch, ignoring any code immediately
535+
// 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.
536539
fn generate_fake_pool_reloc_for_addr_mapping(
537-
address: u64,
540+
func_address: u64,
538541
code: &[u8],
539542
relocations: &[ObjReloc],
540543
) -> HashMap<u32, ObjReloc> {
541-
let mut active_pool_relocs = HashMap::new();
544+
let mut visited_ins_addrs = HashSet::new();
542545
let mut pool_reloc_for_addr = HashMap::new();
543-
for (cur_addr, ins) in InsIter::new(code, address as u32) {
544-
let simplified = ins.simplified();
545-
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
546-
547-
if let Some(reloc) = reloc {
548-
// This instruction has a real relocation, so it may be a pool load we want to keep
549-
// track of.
550-
let args = &simplified.args;
551-
match (ins.op, args[0], args[1], args[2]) {
552-
(
553-
// `lis` + `addi`
554-
Opcode::Addi,
555-
Argument::GPR(addr_dst_gpr),
556-
Argument::GPR(_addr_src_gpr),
557-
Argument::Simm(_simm),
558-
) => {
559-
active_pool_relocs.insert(addr_dst_gpr.0, reloc.clone());
560-
}
561-
(
562-
// `lis` + `ori`
563-
Opcode::Ori,
564-
Argument::GPR(addr_dst_gpr),
565-
Argument::GPR(_addr_src_gpr),
566-
Argument::Uimm(_uimm),
567-
) => {
568-
active_pool_relocs.insert(addr_dst_gpr.0, reloc.clone());
546+
let mut ins_iters_with_gpr_state =
547+
vec![(InsIter::new(code, func_address as u32), HashMap::new())];
548+
while let Some((ins_iter, mut gpr_pool_relocs)) = ins_iters_with_gpr_state.pop() {
549+
for (cur_addr, ins) in ins_iter {
550+
if visited_ins_addrs.contains(&cur_addr) {
551+
// Avoid getting stuck in an infinite loop when following looping branches.
552+
break;
553+
}
554+
visited_ins_addrs.insert(cur_addr);
555+
556+
let simplified = ins.simplified();
557+
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
558+
559+
let mut branch_dest = None;
560+
for arg in simplified.args_iter() {
561+
if let Argument::BranchDest(dest) = arg {
562+
let dest = cur_addr.wrapping_add_signed(dest.0);
563+
branch_dest = Some(dest);
564+
break;
569565
}
570-
(Opcode::B, _, _, _) => {
571-
if simplified.mnemonic == "bl" {
572-
// When encountering a function call, clear any active pool relocations from
573-
// the volatile registers (r0, r3-r12), but not the nonvolatile registers.
574-
active_pool_relocs.remove(&0);
575-
for gpr in 3..12 {
576-
active_pool_relocs.remove(&gpr);
566+
}
567+
if let Some(branch_dest) = branch_dest {
568+
if branch_dest >= func_address as u32
569+
&& (branch_dest - func_address as u32) < code.len() as u32
570+
{
571+
let dest_offset_into_func = branch_dest - func_address as u32;
572+
let dest_code_slice = &code[dest_offset_into_func as usize..];
573+
match ins.op {
574+
Opcode::Bc => {
575+
// Conditional branch.
576+
// Add the branch destination to the queue to do later.
577+
ins_iters_with_gpr_state.push((
578+
InsIter::new(dest_code_slice, branch_dest),
579+
gpr_pool_relocs.clone(),
580+
));
581+
// Then continue on with the current iterator.
577582
}
583+
Opcode::B => {
584+
if simplified.mnemonic != "bl" {
585+
// Unconditional branch.
586+
// Add the branch destination to the queue.
587+
ins_iters_with_gpr_state.push((
588+
InsIter::new(dest_code_slice, branch_dest),
589+
gpr_pool_relocs.clone(),
590+
));
591+
// Break out of the current iterator so we can do the newly added one.
592+
break;
593+
}
594+
}
595+
_ => unreachable!(),
578596
}
579597
}
580-
_ => {
581-
clear_overwritten_gprs(ins, &mut active_pool_relocs);
582-
}
583598
}
584-
} else if let Some((offset, addr_src_gpr, addr_dst_gpr)) =
585-
get_offset_and_addr_gpr_for_possible_pool_reference(ins.op, &simplified)
586-
{
587-
// This instruction doesn't have a real relocation, so it may be a reference to one of
588-
// the already-loaded pools.
589-
if let Some(pool_reloc) = active_pool_relocs.get(&addr_src_gpr.0) {
590-
if let Some(fake_pool_reloc) = make_fake_pool_reloc(offset, cur_addr, pool_reloc) {
591-
pool_reloc_for_addr.insert(cur_addr, fake_pool_reloc);
599+
600+
if let Some(reloc) = reloc {
601+
// This instruction has a real relocation, so it may be a pool load we want to keep
602+
// track of.
603+
let args = &simplified.args;
604+
match (ins.op, args[0], args[1], args[2]) {
605+
(
606+
// `lis` + `addi`
607+
Opcode::Addi,
608+
Argument::GPR(addr_dst_gpr),
609+
Argument::GPR(_addr_src_gpr),
610+
Argument::Simm(_simm),
611+
) => {
612+
gpr_pool_relocs.insert(addr_dst_gpr.0, reloc.clone());
613+
}
614+
(
615+
// `lis` + `ori`
616+
Opcode::Ori,
617+
Argument::GPR(addr_dst_gpr),
618+
Argument::GPR(_addr_src_gpr),
619+
Argument::Uimm(_uimm),
620+
) => {
621+
gpr_pool_relocs.insert(addr_dst_gpr.0, reloc.clone());
622+
}
623+
(Opcode::B, _, _, _) => {
624+
if simplified.mnemonic == "bl" {
625+
// When encountering a function call, clear any active pool relocations from
626+
// the volatile registers (r0, r3-r12), but not the nonvolatile registers.
627+
gpr_pool_relocs.remove(&0);
628+
for gpr in 3..12 {
629+
gpr_pool_relocs.remove(&gpr);
630+
}
631+
}
632+
}
633+
_ => {
634+
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
635+
}
592636
}
593-
if let Some(addr_dst_gpr) = addr_dst_gpr {
594-
// If the address of the pool relocation got copied into another register, we
595-
// need to keep track of it in that register too as future instructions may
596-
// reference the symbol indirectly via this new register, instead of the
597-
// register the symbol's address was originally loaded into.
598-
// For example, the start of the function might `lis` + `addi` the start of the
599-
// ...data pool into r25, and then later the start of a loop will `addi` r25
600-
// with the offset within the .data section of an array variable into r21.
601-
// Then the body of the loop will `lwzx` one of the array elements from r21.
602-
let mut new_reloc = pool_reloc.clone();
603-
new_reloc.addend += offset as i64;
604-
active_pool_relocs.insert(addr_dst_gpr.0, new_reloc);
637+
} else if let Some((offset, addr_src_gpr, addr_dst_gpr)) =
638+
get_offset_and_addr_gpr_for_possible_pool_reference(ins.op, &simplified)
639+
{
640+
// This instruction doesn't have a real relocation, so it may be a reference to one of
641+
// the already-loaded pools.
642+
if let Some(pool_reloc) = gpr_pool_relocs.get(&addr_src_gpr.0) {
643+
if let Some(fake_pool_reloc) =
644+
make_fake_pool_reloc(offset, cur_addr, pool_reloc)
645+
{
646+
pool_reloc_for_addr.insert(cur_addr, fake_pool_reloc);
647+
}
648+
if let Some(addr_dst_gpr) = addr_dst_gpr {
649+
// If the address of the pool relocation got copied into another register, we
650+
// need to keep track of it in that register too as future instructions may
651+
// reference the symbol indirectly via this new register, instead of the
652+
// register the symbol's address was originally loaded into.
653+
// For example, the start of the function might `lis` + `addi` the start of the
654+
// ...data pool into r25, and then later the start of a loop will `addi` r25
655+
// with the offset within the .data section of an array variable into r21.
656+
// Then the body of the loop will `lwzx` one of the array elements from r21.
657+
let mut new_reloc = pool_reloc.clone();
658+
new_reloc.addend += offset as i64;
659+
gpr_pool_relocs.insert(addr_dst_gpr.0, new_reloc);
660+
} else {
661+
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
662+
}
605663
} else {
606-
clear_overwritten_gprs(ins, &mut active_pool_relocs);
664+
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
607665
}
608666
} else {
609-
clear_overwritten_gprs(ins, &mut active_pool_relocs);
667+
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
610668
}
611-
} else {
612-
clear_overwritten_gprs(ins, &mut active_pool_relocs);
613669
}
614670
}
615671

0 commit comments

Comments
 (0)