diff options
Diffstat (limited to 'bfd/elf32-arm.c')
-rw-r--r-- | bfd/elf32-arm.c | 53 |
1 files changed, 42 insertions, 11 deletions
diff --git a/bfd/elf32-arm.c b/bfd/elf32-arm.c index 2527e5e..607ba04 100644 --- a/bfd/elf32-arm.c +++ b/bfd/elf32-arm.c @@ -2857,6 +2857,7 @@ arm_type_of_stub (struct bfd_link_info *info, int thumb2; int thumb_only; enum elf32_arm_stub_type stub_type = arm_stub_none; + int use_plt = 0; /* We don't know the actual type of destination in case it is of type STT_SECTION: give up. */ @@ -2878,20 +2879,38 @@ arm_type_of_stub (struct bfd_link_info *info, r_type = ELF32_R_TYPE (rel->r_info); - /* If the call will go through a PLT entry then we do not need - glue. */ + /* Keep a simpler condition, for the sake of clarity. */ if (globals->splt != NULL && hash != NULL && hash->root.plt.offset != (bfd_vma) -1) - return stub_type; + { + use_plt = 1; + /* Note when dealing with PLT entries: the main PLT stub is in + ARM mode, so if the branch is in Thumb mode, another + Thumb->ARM stub will be inserted later just before the ARM + PLT stub. We don't take this extra distance into account + here, because if a long branch stub is needed, we'll add a + Thumb->Arm one and branch directly to the ARM PLT entry + because it avoids spreading offset corrections in several + places. */ + } if (r_type == R_ARM_THM_CALL) { + /* Handle cases where: + - this call goes too far (different Thumb/Thumb2 max + distance) + - it's a Thumb->Arm call and blx is not available. A stub is + needed in this case, but only if this call is not through a + PLT entry. Indeed, PLT stubs handle mode switching already. + */ if ((!thumb2 && (branch_offset > THM_MAX_FWD_BRANCH_OFFSET || (branch_offset < THM_MAX_BWD_BRANCH_OFFSET))) || (thumb2 && (branch_offset > THM2_MAX_FWD_BRANCH_OFFSET || (branch_offset < THM2_MAX_BWD_BRANCH_OFFSET))) - || ((st_type != STT_ARM_TFUNC) && !globals->use_blx)) + || ((st_type != STT_ARM_TFUNC) + && ((r_type == R_ARM_THM_CALL) && !globals->use_blx) + && !use_plt)) { if (st_type == STT_ARM_TFUNC) { @@ -6049,9 +6068,11 @@ elf32_arm_final_link_relocate (reloc_howto_type * howto, /* Handle relocations which should use the PLT entry. ABS32/REL32 will use the symbol's value, which may point to a PLT entry, but we don't need to handle that here. If we created a PLT entry, all - branches in this object should go to it. */ + branches in this object should go to it, except if the PLT is too + far away, in which case a long branch stub should be inserted. */ if ((r_type != R_ARM_ABS32 && r_type != R_ARM_REL32 - && r_type != R_ARM_ABS32_NOI && r_type != R_ARM_REL32_NOI) + && r_type != R_ARM_ABS32_NOI && r_type != R_ARM_REL32_NOI + && r_type != R_ARM_CALL) && h != NULL && splt != NULL && h->plt.offset != (bfd_vma) -1) @@ -6208,11 +6229,6 @@ elf32_arm_final_link_relocate (reloc_howto_type * howto, bfd_signed_vma branch_offset; struct elf32_arm_stub_hash_entry *stub_entry = NULL; - from = (input_section->output_section->vma - + input_section->output_offset - + rel->r_offset); - branch_offset = (bfd_signed_vma)(value - from); - if (r_type == R_ARM_XPC25) { /* Check for Arm calling Arm function. */ @@ -6244,6 +6260,21 @@ elf32_arm_final_link_relocate (reloc_howto_type * howto, destination is too far or we are changing mode. */ if (r_type == R_ARM_CALL) { + /* If the call goes through a PLT entry, make sure to + check distance to the right destination address. */ + if (h != NULL && splt != NULL && h->plt.offset != (bfd_vma) -1) + { + value = (splt->output_section->vma + + splt->output_offset + + h->plt.offset); + *unresolved_reloc_p = FALSE; + } + + from = (input_section->output_section->vma + + input_section->output_offset + + rel->r_offset); + branch_offset = (bfd_signed_vma)(value - from); + if (branch_offset > ARM_MAX_FWD_BRANCH_OFFSET || branch_offset < ARM_MAX_BWD_BRANCH_OFFSET || sym_flags == STT_ARM_TFUNC) |