|
1 | 1 | use std::{ |
2 | 2 | borrow::Cow, |
3 | | - collections::{BTreeMap, HashMap}, |
| 3 | + collections::{BTreeMap, HashMap, HashSet}, |
4 | 4 | }; |
5 | 5 |
|
6 | 6 | use anyhow::{bail, ensure, Result}; |
@@ -458,12 +458,12 @@ fn get_offset_and_addr_gpr_for_possible_pool_reference( |
458 | 458 |
|
459 | 459 | // Remove the relocation we're keeping track of in a particular register when an instruction reuses |
460 | 460 | // 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>) { |
462 | 462 | let mut def_args = Arguments::default(); |
463 | 463 | ins.parse_defs(&mut def_args); |
464 | 464 | for arg in def_args { |
465 | 465 | if let Argument::GPR(gpr) = arg { |
466 | | - active_pool_relocs.remove(&gpr.0); |
| 466 | + gpr_pool_relocs.remove(&gpr.0); |
467 | 467 | } |
468 | 468 | } |
469 | 469 | } |
@@ -526,90 +526,146 @@ fn make_fake_pool_reloc(offset: i16, cur_addr: u32, pool_reloc: &ObjReloc) -> Op |
526 | 526 | // of pooled data relocations in them, finding which instructions load data from those addresses, |
527 | 527 | // and constructing a mapping of the address of that instruction to a "fake pool relocation" that |
528 | 528 | // 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. |
536 | 539 | fn generate_fake_pool_reloc_for_addr_mapping( |
537 | | - address: u64, |
| 540 | + func_address: u64, |
538 | 541 | code: &[u8], |
539 | 542 | relocations: &[ObjReloc], |
540 | 543 | ) -> HashMap<u32, ObjReloc> { |
541 | | - let mut active_pool_relocs = HashMap::new(); |
| 544 | + let mut visited_ins_addrs = HashSet::new(); |
542 | 545 | 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; |
569 | 565 | } |
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. |
577 | 582 | } |
| 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!(), |
578 | 596 | } |
579 | 597 | } |
580 | | - _ => { |
581 | | - clear_overwritten_gprs(ins, &mut active_pool_relocs); |
582 | | - } |
583 | 598 | } |
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 | + } |
592 | 636 | } |
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 | + } |
605 | 663 | } else { |
606 | | - clear_overwritten_gprs(ins, &mut active_pool_relocs); |
| 664 | + clear_overwritten_gprs(ins, &mut gpr_pool_relocs); |
607 | 665 | } |
608 | 666 | } else { |
609 | | - clear_overwritten_gprs(ins, &mut active_pool_relocs); |
| 667 | + clear_overwritten_gprs(ins, &mut gpr_pool_relocs); |
610 | 668 | } |
611 | | - } else { |
612 | | - clear_overwritten_gprs(ins, &mut active_pool_relocs); |
613 | 669 | } |
614 | 670 | } |
615 | 671 |
|
|
0 commit comments