#define LD_R2_0R12 0xe84c0000 /* ld %r2,0(%r12) */
#define ADD_R2_R2_R12 0x7c426214 /* add %r2,%r2,%r12 */
+#define LI_R11_0 0x39600000 /* li %r11,0 */
#define LIS_R2 0x3c400000 /* lis %r2,xxx@ha */
+#define LIS_R11 0x3d600000 /* lis %r11,xxx@ha */
#define LIS_R12 0x3d800000 /* lis %r12,xxx@ha */
#define ADDIS_R2_R12 0x3c4c0000 /* addis %r2,%r12,xxx@ha */
#define ADDIS_R12_R2 0x3d820000 /* addis %r12,%r2,xxx@ha */
#define ADDIS_R12_R11 0x3d8b0000 /* addis %r12,%r11,xxx@ha */
#define ADDIS_R12_R12 0x3d8c0000 /* addis %r12,%r12,xxx@ha */
#define ORIS_R12_R12_0 0x658c0000 /* oris %r12,%r12,xxx@hi */
+#define ORI_R11_R11_0 0x616b0000 /* ori %r11,%r11,xxx@l */
#define ORI_R12_R12_0 0x618c0000 /* ori %r12,%r12,xxx@l */
#define LD_R12_0R12 0xe98c0000 /* ld %r12,xxx@l(%r12) */
+#define SLDI_R11_R11_34 0x796b1746 /* sldi %r11,%r11,34 */
#define SLDI_R12_R12_32 0x799c07c6 /* sldi %r12,%r12,32 */
#define LDX_R12_R11_R12 0x7d8b602a /* ldx %r12,%r11,%r12 */
#define ADD_R12_R11_R12 0x7d8b6214 /* add %r12,%r11,%r12 */
+#define PADDI_R12_PC 0x0610000039800000ULL
+#define PLD_R12_PC 0x04100000e5800000ULL
#define PNOP 0x0700000000000000ULL
/* __glink_PLTresolve stub instructions. We enter with the index in R0. */
. mtctr %r12
. bctr
+ There are also ELFv1 powerxx variants of these stubs.
+ ppc_stub_long_branch_notoc:
+ . paddi %r12,dest@pcrel
+ . b dest
+ ppc_stub_plt_branch_notoc:
+ . lis %r11,(dest-1f)@highesta34
+ . ori %r11,%r11,(dest-1f)@highera34
+ . sldi %r11,%r11,34
+ . 1: paddi %r12,dest@pcrel
+ . add %r12,%r11,%r12
+ . mtctr %r12
+ . bctr
+ ppc_stub_plt_call_notoc:
+ . lis %r11,(xxx-1f)@highesta34
+ . ori %r11,%r11,(xxx-1f)@highera34
+ . sldi %r11,%r11,34
+ . 1: paddi %r12,xxx@pcrel
+ . ldx %r12,%r11,%r12
+ . mtctr %r12
+ . bctr
+
In cases where the high instructions would add zero, they are
omitted and following instructions modified in some cases.
+ For example, a powerxx ppc_stub_plt_call_notoc might simplify down
+ to
+ . pld %r12,xxx@pcrel
+ . mtctr %r12
+ . bctr
For a given stub group (a set of sections all using the same toc
pointer value) there will be just one stub type used for any
/* Whether calls are made via the PLT from NOTOC functions. */
unsigned int notoc_plt:1;
+ /* Whether to use powerxx instructions in linkage stubs. */
+ unsigned int powerxx_stubs:1;
+
/* Incremented every time we size stubs. */
unsigned int stub_iteration;
r_type = ELF64_R_TYPE (rel->r_info);
switch (r_type)
{
+ case R_PPC64_D34:
+ case R_PPC64_D34_LO:
+ case R_PPC64_D34_HI30:
+ case R_PPC64_D34_HA30:
+ case R_PPC64_D28:
+ htab->powerxx_stubs = 1;
+ /* Fall through. */
default:
/* Somewhat foolishly, because the ABIs don't specifically
allow it, ppc64 gas and ld support GOT and PLT relocs
used to specify the pcrel offset. */
sym_addend = rel->r_addend;
break;
+
+ case R_PPC64_PCREL34:
case R_PPC64_GOT_PCREL34:
case R_PPC64_PLT_PCREL34:
case R_PPC64_PLT_PCREL34_NOTOC:
+ case R_PPC64_PCREL28:
+ htab->powerxx_stubs = 1;
sym_addend = 0;
break;
}
#define PPC_LO(v) ((v) & 0xffff)
#define PPC_HI(v) (((v) >> 16) & 0xffff)
#define PPC_HA(v) PPC_HI ((v) + 0x8000)
+#define D34(v) \
+ ((((v) & 0x3ffff0000ULL) << 16) | (v & 0xffff))
+#define HA34(v) ((v + (1ULL << 33)) >> 34)
/* Called via elf_link_hash_traverse from ppc64_elf_size_dynamic_sections
to set up space for global entry stubs. These are put in glink,
return r;
}
+static bfd_byte *
+build_powerxx_offset (bfd *abfd, bfd_byte *p, bfd_vma off, int odd,
+ bfd_boolean load)
+{
+ uint64_t insn;
+ if (off - odd + (1ULL << 33) < 1ULL << 34)
+ {
+ off -= odd;
+ if (odd)
+ {
+ bfd_put_32 (abfd, NOP, p);
+ p += 4;
+ }
+ if (load)
+ insn = PLD_R12_PC;
+ else
+ insn = PADDI_R12_PC;
+ insn |= D34 (off);
+ bfd_put_32 (abfd, insn >> 32, p);
+ p += 4;
+ bfd_put_32 (abfd, insn, p);
+ }
+ /* The minimum value for paddi is -0x200000000. The minimum value
+ for li is -0x8000, which when shifted by 34 and added gives a
+ minimum value of -0x2000200000000. The maximum value is
+ 0x1ffffffff+0x7fff<<34 which is 0x2000200000000-1. */
+ else if (off - (8 - odd) + (0x20002ULL << 32) < 0x40004ULL << 32)
+ {
+ off -= 8 - odd;
+ bfd_put_32 (abfd, LI_R11_0 | (HA34 (off) & 0xffff), p);
+ p += 4;
+ if (!odd)
+ {
+ bfd_put_32 (abfd, SLDI_R11_R11_34, p);
+ p += 4;
+ }
+ insn = PADDI_R12_PC | D34 (off);
+ bfd_put_32 (abfd, insn >> 32, p);
+ p += 4;
+ bfd_put_32 (abfd, insn, p);
+ p += 4;
+ if (odd)
+ {
+ bfd_put_32 (abfd, SLDI_R11_R11_34, p);
+ p += 4;
+ }
+ if (load)
+ bfd_put_32 (abfd, LDX_R12_R11_R12, p);
+ else
+ bfd_put_32 (abfd, ADD_R12_R11_R12, p);
+ }
+ else
+ {
+ off -= odd + 8;
+ bfd_put_32 (abfd, LIS_R11 | ((HA34 (off) >> 16) & 0x3fff), p);
+ p += 4;
+ bfd_put_32 (abfd, ORI_R11_R11_0 | (HA34 (off) & 0xffff), p);
+ p += 4;
+ if (odd)
+ {
+ bfd_put_32 (abfd, SLDI_R11_R11_34, p);
+ p += 4;
+ }
+ insn = PADDI_R12_PC | D34 (off);
+ bfd_put_32 (abfd, insn >> 32, p);
+ p += 4;
+ bfd_put_32 (abfd, insn, p);
+ p += 4;
+ if (!odd)
+ {
+ bfd_put_32 (abfd, SLDI_R11_R11_34, p);
+ p += 4;
+ }
+ if (load)
+ bfd_put_32 (abfd, LDX_R12_R11_R12, p);
+ else
+ bfd_put_32 (abfd, ADD_R12_R11_R12, p);
+ }
+ p += 4;
+ return p;
+}
+
+static unsigned int
+size_powerxx_offset (bfd_vma off, int odd)
+{
+ if (off - odd + (1ULL << 33) < 1ULL << 34)
+ return odd + 8;
+ else if (off - (8 - odd) + (0x20002ULL << 32) < 0x40004ULL << 32)
+ return 20;
+ else
+ return 24;
+}
+
+static unsigned int
+num_relocs_for_powerxx_offset (bfd_vma off, int odd)
+{
+ if (off - odd + (1ULL << 33) < 1ULL << 34)
+ return 1;
+ else if (off - (8 - odd) + (0x20002ULL << 32) < 0x40004ULL << 32)
+ return 2;
+ else
+ return 3;
+}
+
+static Elf_Internal_Rela *
+emit_relocs_for_powerxx_offset (struct bfd_link_info *info,
+ Elf_Internal_Rela *r, bfd_vma roff,
+ bfd_vma targ, bfd_vma off, int odd)
+{
+ if (off - odd + (1ULL << 33) < 1ULL << 34)
+ roff += odd;
+ else if (off - (8 - odd) + (0x20002ULL << 32) < 0x40004ULL << 32)
+ {
+ int d_offset = bfd_big_endian (info->output_bfd) ? 2 : 0;
+ r->r_offset = roff + d_offset;
+ r->r_addend = targ + 8 - odd - d_offset;
+ r->r_info = ELF64_R_INFO (0, R_PPC64_REL16_HIGHERA34);
+ ++r;
+ roff += 8 - odd;
+ }
+ else
+ {
+ int d_offset = bfd_big_endian (info->output_bfd) ? 2 : 0;
+ r->r_offset = roff + d_offset;
+ r->r_addend = targ + 8 + odd - d_offset;
+ r->r_info = ELF64_R_INFO (0, R_PPC64_REL16_HIGHESTA34);
+ ++r;
+ roff += 4;
+ r->r_offset = roff + d_offset;
+ r->r_addend = targ + 4 + odd - d_offset;
+ r->r_info = ELF64_R_INFO (0, R_PPC64_REL16_HIGHERA34);
+ ++r;
+ roff += 4 + odd;
+ }
+ r->r_offset = roff;
+ r->r_addend = targ;
+ r->r_info = ELF64_R_INFO (0, R_PPC64_PCREL34);
+ return r;
+}
+
/* Emit .eh_frame opcode to advance pc by DELTA. */
static bfd_byte *
if (stub_entry->stub_type >= ppc_stub_plt_call_notoc)
{
- size = 8 + size_offset (off - 8);
+ if (htab->powerxx_stubs)
+ {
+ bfd_vma start = (stub_entry->stub_offset
+ + stub_entry->group->stub_sec->output_offset
+ + stub_entry->group->stub_sec->output_section->vma);
+ if (stub_entry->stub_type > ppc_stub_plt_call_notoc)
+ start += 4;
+ size = 8 + size_powerxx_offset (off, start & 4);
+ }
+ else
+ size = 8 + size_offset (off - 8);
if (stub_entry->stub_type > ppc_stub_plt_call_notoc)
size += 4;
return size;
Elf_Internal_Rela *r;
asection *plt;
int num_rel;
+ int odd;
/* Massage our args to the form they really have. */
stub_entry = (struct ppc_stub_hash_entry *) gen_entry;
targ = (stub_entry->target_value
+ stub_entry->target_section->output_offset
+ stub_entry->target_section->output_section->vma);
+ odd = off & 4;
off = targ - off;
relp = p;
num_rel = 0;
- /* The notoc stubs calculate their target (either a PLT entry or
- the global entry point of a function) relative to the PC
- returned by the "bcl" two instructions past the start of the
- sequence emitted by build_offset. The offset is therefore 8
- less than calculated from the start of the sequence. */
- off -= 8;
- p = build_offset (htab->params->stub_bfd, p, off,
- stub_entry->stub_type >= ppc_stub_plt_call_notoc);
+ if (htab->powerxx_stubs)
+ {
+ bfd_boolean load = stub_entry->stub_type >= ppc_stub_plt_call_notoc;
+ p = build_powerxx_offset (htab->params->stub_bfd, p, off, odd, load);
+ }
+ else
+ {
+ /* The notoc stubs calculate their target (either a PLT entry or
+ the global entry point of a function) relative to the PC
+ returned by the "bcl" two instructions past the start of the
+ sequence emitted by build_offset. The offset is therefore 8
+ less than calculated from the start of the sequence. */
+ off -= 8;
+ p = build_offset (htab->params->stub_bfd, p, off,
+ stub_entry->stub_type >= ppc_stub_plt_call_notoc);
+ }
+
if (stub_entry->stub_type <= ppc_stub_long_branch_both)
{
bfd_vma from;
if (info->emitrelocations)
{
- bfd_vma roff;
- num_rel += num_relocs_for_offset (off);
+ bfd_vma roff = relp - stub_entry->group->stub_sec->contents;
+ if (htab->powerxx_stubs)
+ num_rel += num_relocs_for_powerxx_offset (off, odd);
+ else
+ {
+ num_rel += num_relocs_for_offset (off);
+ roff += 16;
+ }
r = get_relocs (stub_entry->group->stub_sec, num_rel);
if (r == NULL)
return FALSE;
- roff = relp + 16 - stub_entry->group->stub_sec->contents;
- r = emit_relocs_for_offset (info, r, roff, targ, off);
+ if (htab->powerxx_stubs)
+ r = emit_relocs_for_powerxx_offset (info, r, roff, targ, off, odd);
+ else
+ r = emit_relocs_for_offset (info, r, roff, targ, off);
if (stub_entry->stub_type == ppc_stub_long_branch_notoc
|| stub_entry->stub_type == ppc_stub_long_branch_both)
{
}
}
- if (htab->glink_eh_frame != NULL
- && htab->glink_eh_frame->size != 0)
+ if (!htab->powerxx_stubs
+ && htab->glink_eh_frame != NULL
+ && htab->glink_eh_frame->size != 0)
{
bfd_byte *base, *eh;
unsigned int lr_used, delta;
struct ppc_link_hash_table *htab;
asection *plt;
bfd_vma targ, off, r2off;
- unsigned int size, extra, lr_used, delta;
+ unsigned int size, extra, lr_used, delta, odd;
/* Massage our args to the form they really have. */
stub_entry = (struct ppc_stub_hash_entry *) gen_entry;
targ = (stub_entry->target_value
+ stub_entry->target_section->output_offset
+ stub_entry->target_section->output_section->vma);
+ odd = off & 4;
off = targ - off;
if (info->emitrelocations)
{
- stub_entry->group->stub_sec->reloc_count
- += num_relocs_for_offset (off);
+ unsigned int num_rel;
+ if (htab->powerxx_stubs)
+ num_rel = num_relocs_for_powerxx_offset (off, odd);
+ else
+ num_rel = num_relocs_for_offset (off - 8);
+ stub_entry->group->stub_sec->reloc_count += num_rel;
stub_entry->group->stub_sec->flags |= SEC_RELOC;
}
- extra = size_offset (off - 8);
+ if (htab->powerxx_stubs)
+ extra = size_powerxx_offset (off, odd);
+ else
+ extra = size_offset (off - 8);
/* Include branch insn plus those in the offset sequence. */
size += 4 + extra;
/* The branch insn is at the end, or "extra" bytes along. So
calculated. */
off -= extra;
- /* After the bcl, lr has been modified so we need to emit
- .eh_frame info saying the return address is in r12. */
- lr_used = stub_entry->stub_offset + 8;
- if (stub_entry->stub_type == ppc_stub_long_branch_both)
- lr_used += 4;
- /* The eh_frame info will consist of a DW_CFA_advance_loc or
- variant, DW_CFA_register, 65, 12, DW_CFA_advance_loc+2,
- DW_CFA_restore_extended 65. */
- delta = lr_used - stub_entry->group->lr_restore;
- stub_entry->group->eh_size += eh_advance_size (delta) + 6;
- stub_entry->group->lr_restore = lr_used + 8;
+ if (!htab->powerxx_stubs)
+ {
+ /* After the bcl, lr has been modified so we need to emit
+ .eh_frame info saying the return address is in r12. */
+ lr_used = stub_entry->stub_offset + 8;
+ if (stub_entry->stub_type == ppc_stub_long_branch_both)
+ lr_used += 4;
+ /* The eh_frame info will consist of a DW_CFA_advance_loc or
+ variant, DW_CFA_register, 65, 12, DW_CFA_advance_loc+2,
+ DW_CFA_restore_extended 65. */
+ delta = lr_used - stub_entry->group->lr_restore;
+ stub_entry->group->eh_size += eh_advance_size (delta) + 6;
+ stub_entry->group->lr_restore = lr_used + 8;
+ }
/* If the branch can't reach, use a plt_branch. */
if (off + (1 << 25) >= (bfd_vma) (1 << 26))
plt = htab->pltlocal;
}
targ += plt->output_offset + plt->output_section->vma;
+ odd = off & 4;
off = targ - off;
if (htab->params->plt_stub_align != 0)
if (info->emitrelocations)
{
- stub_entry->group->stub_sec->reloc_count
- += num_relocs_for_offset (off - 8);
+ unsigned int num_rel;
+ if (htab->powerxx_stubs)
+ num_rel = num_relocs_for_powerxx_offset (off, odd);
+ else
+ num_rel = num_relocs_for_offset (off - 8);
+ stub_entry->group->stub_sec->reloc_count += num_rel;
stub_entry->group->stub_sec->flags |= SEC_RELOC;
}
size = plt_stub_size (htab, stub_entry, off);
- /* After the bcl, lr has been modified so we need to emit
- .eh_frame info saying the return address is in r12. */
- lr_used = stub_entry->stub_offset + 8;
- if (stub_entry->stub_type == ppc_stub_plt_call_both)
- lr_used += 4;
- /* The eh_frame info will consist of a DW_CFA_advance_loc or
- variant, DW_CFA_register, 65, 12, DW_CFA_advance_loc+2,
- DW_CFA_restore_extended 65. */
- delta = lr_used - stub_entry->group->lr_restore;
- stub_entry->group->eh_size += eh_advance_size (delta) + 6;
- stub_entry->group->lr_restore = lr_used + 8;
+ if (!htab->powerxx_stubs)
+ {
+ /* After the bcl, lr has been modified so we need to emit
+ .eh_frame info saying the return address is in r12. */
+ lr_used = stub_entry->stub_offset + 8;
+ if (stub_entry->stub_type == ppc_stub_plt_call_both)
+ lr_used += 4;
+ /* The eh_frame info will consist of a DW_CFA_advance_loc or
+ variant, DW_CFA_register, 65, 12, DW_CFA_advance_loc+2,
+ DW_CFA_restore_extended 65. */
+ delta = lr_used - stub_entry->group->lr_restore;
+ stub_entry->group->eh_size += eh_advance_size (delta) + 6;
+ stub_entry->group->lr_restore = lr_used + 8;
+ }
break;
case ppc_stub_plt_call: