aboutsummaryrefslogtreecommitdiff
path: root/bfd/elf32-arm.c
diff options
context:
space:
mode:
Diffstat (limited to 'bfd/elf32-arm.c')
-rw-r--r--bfd/elf32-arm.c53
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)