/* X86-64 specific support for ELF
- Copyright (C) 2000-2015 Free Software Foundation, Inc.
+ Copyright (C) 2000-2016 Free Software Foundation, Inc.
Contributed by Jan Hubicka <jh@suse.cz>.
This file is part of BFD, the Binary File Descriptor library.
#include "dwarf2.h"
#include "libiberty.h"
+#include "opcode/i386.h"
#include "elf/x86-64.h"
#ifdef CORE_HEADER
HOWTO(R_X86_64_PLT32_BND, 0, 2, 32, TRUE, 0, complain_overflow_signed,
bfd_elf_generic_reloc, "R_X86_64_PLT32_BND", FALSE, 0xffffffff, 0xffffffff,
TRUE),
+ HOWTO(R_X86_64_GOTPCRELX, 0, 2, 32, TRUE, 0, complain_overflow_signed,
+ bfd_elf_generic_reloc, "R_X86_64_GOTPCRELX", FALSE, 0xffffffff,
+ 0xffffffff, TRUE),
+ HOWTO(R_X86_64_REX_GOTPCRELX, 0, 2, 32, TRUE, 0, complain_overflow_signed,
+ bfd_elf_generic_reloc, "R_X86_64_REX_GOTPCRELX", FALSE, 0xffffffff,
+ 0xffffffff, TRUE),
/* We have a gap in the reloc numbers here.
R_X86_64_standard counts the number up to this point, and
R_X86_64_vt_offset is the value to subtract from a reloc type of
R_X86_64_GNU_VT* to form an index into this table. */
-#define R_X86_64_standard (R_X86_64_PLT32_BND + 1)
+#define R_X86_64_standard (R_X86_64_REX_GOTPCRELX + 1)
#define R_X86_64_vt_offset (R_X86_64_GNU_VTINHERIT - R_X86_64_standard)
/* GNU extension to record C++ vtable hierarchy. */
{ BFD_RELOC_X86_64_TLSDESC_CALL, R_X86_64_TLSDESC_CALL, },
{ BFD_RELOC_X86_64_TLSDESC, R_X86_64_TLSDESC, },
{ BFD_RELOC_X86_64_IRELATIVE, R_X86_64_IRELATIVE, },
- { BFD_RELOC_X86_64_PC32_BND, R_X86_64_PC32_BND,},
- { BFD_RELOC_X86_64_PLT32_BND, R_X86_64_PLT32_BND,},
+ { BFD_RELOC_X86_64_PC32_BND, R_X86_64_PC32_BND, },
+ { BFD_RELOC_X86_64_PLT32_BND, R_X86_64_PLT32_BND, },
+ { BFD_RELOC_X86_64_GOTPCRELX, R_X86_64_GOTPCRELX, },
+ { BFD_RELOC_X86_64_REX_GOTPCRELX, R_X86_64_REX_GOTPCRELX, },
{ BFD_RELOC_VTABLE_INHERIT, R_X86_64_GNU_VTINHERIT, },
{ BFD_RELOC_VTABLE_ENTRY, R_X86_64_GNU_VTENTRY, },
};
/* Rename some of the generic section flags to better document how they
are used here. */
-#define need_convert_mov_to_lea sec_flg0
+#define need_convert_load sec_flg0
/* Look through the relocs for a section during the first phase, and
calculate needed space in the global offset table, procedure
case R_X86_64_32S:
case R_X86_64_PC64:
case R_X86_64_GOTPCREL:
+ case R_X86_64_GOTPCRELX:
+ case R_X86_64_REX_GOTPCRELX:
case R_X86_64_GOTPCREL64:
if (htab->elf.dynobj == NULL)
htab->elf.dynobj = abfd;
case R_X86_64_GOT32:
case R_X86_64_GOTPCREL:
+ case R_X86_64_GOTPCRELX:
+ case R_X86_64_REX_GOTPCRELX:
case R_X86_64_TLSGD:
case R_X86_64_GOT64:
case R_X86_64_GOTPCREL64:
return FALSE;
}
- if (r_type == R_X86_64_GOTPCREL
+ if ((r_type == R_X86_64_GOTPCREL
+ || r_type == R_X86_64_GOTPCRELX
+ || r_type == R_X86_64_REX_GOTPCRELX)
&& (h == NULL || h->type != STT_GNU_IFUNC))
- sec->need_convert_mov_to_lea = 1;
+ sec->need_convert_load = 1;
}
return TRUE;
case R_X86_64_GOTTPOFF:
case R_X86_64_GOT32:
case R_X86_64_GOTPCREL:
+ case R_X86_64_GOTPCRELX:
+ case R_X86_64_REX_GOTPCRELX:
case R_X86_64_GOT64:
case R_X86_64_GOTPCREL64:
case R_X86_64_GOTPLT64:
return TRUE;
}
-/* Convert
+/* With the local symbol, foo, we convert
mov foo@GOTPCREL(%rip), %reg
to
lea foo(%rip), %reg
- with the local symbol, foo. */
+ and convert
+ call/jmp *foo@GOTPCREL(%rip)
+ to
+ nop call foo/jmp foo nop
+ When PIC is false, convert
+ test %reg, foo@GOTPCREL(%rip)
+ to
+ test $foo, %reg
+ and convert
+ binop foo@GOTPCREL(%rip), %reg
+ to
+ binop $foo, %reg
+ where binop is one of adc, add, and, cmp, or, sbb, sub, xor
+ instructions. */
static bfd_boolean
-elf_x86_64_convert_mov_to_lea (bfd *abfd, asection *sec,
- struct bfd_link_info *link_info)
+elf_x86_64_convert_load (bfd *abfd, asection *sec,
+ struct bfd_link_info *link_info)
{
Elf_Internal_Shdr *symtab_hdr;
Elf_Internal_Rela *internal_relocs;
/* Nothing to do if there is no need or no output. */
if ((sec->flags & (SEC_CODE | SEC_RELOC)) != (SEC_CODE | SEC_RELOC)
- || sec->need_convert_mov_to_lea == 0
+ || sec->need_convert_load == 0
|| bfd_is_abs_section (sec->output_section))
return TRUE;
asection *tsec;
char symtype;
bfd_vma toff, roff;
+ bfd_signed_vma raddend;
unsigned int opcode;
+ unsigned int modrm;
- if (r_type != R_X86_64_GOTPCREL)
+ if (r_type != R_X86_64_GOTPCREL
+ && r_type != R_X86_64_GOTPCRELX
+ && r_type != R_X86_64_REX_GOTPCRELX)
continue;
roff = irel->r_offset;
+ if (roff < (r_type == R_X86_64_REX_GOTPCRELX ? 3 : 2))
+ continue;
- if (roff < 2)
+ raddend = irel->r_addend;
+ /* Addend for 32-bit PC-relative relocation must be -4. */
+ if (raddend != -4)
continue;
opcode = bfd_get_8 (abfd, contents + roff - 2);
- /* PR ld/18591: Don't convert R_X86_64_GOTPCREL relocation if it
- isn't for mov instruction. */
+ /* It is OK to convert mov to lea. */
if (opcode != 0x8b)
- continue;
+ {
+ /* Only convert R_X86_64_GOTPCRELX and R_X86_64_REX_GOTPCRELX
+ for mov call, jmp or one of adc, add, and, cmp, or, sbb,
+ sub, test, xor instructions. */
+ if (r_type != R_X86_64_GOTPCRELX
+ && r_type != R_X86_64_REX_GOTPCRELX)
+ continue;
+
+ /* It is OK to convert indirect branch to direct branch. */
+ if (opcode != 0xff)
+ {
+ /* It is OK to convert adc, add, and, cmp, or, sbb, sub,
+ test, xor only when PIC is false. */
+ if (bfd_link_pic (link_info))
+ continue;
+ }
+ }
/* Get the symbol referred to by the reloc. */
if (r_symndx < symtab_hdr->sh_info)
symtype = ELF_ST_TYPE (isym->st_info);
- /* STT_GNU_IFUNC must keep R_X86_64_GOTPCREL relocation and
- skip relocation against undefined symbols. */
+ /* STT_GNU_IFUNC must keep GOTPCREL relocations and skip
+ relocation against undefined symbols. */
if (symtype == STT_GNU_IFUNC || isym->st_shndx == SHN_UNDEF)
continue;
|| h->root.type == bfd_link_hash_warning)
h = (struct elf_link_hash_entry *) h->root.u.i.link;
- /* STT_GNU_IFUNC must keep R_X86_64_GOTPCREL relocation. We also
- avoid optimizing _DYNAMIC since ld.so may use its link-time
- address. */
- if (h->type != STT_GNU_IFUNC
+ /* STT_GNU_IFUNC must keep GOTPCREL relocations. We also
+ avoid optimizing GOTPCREL relocations againt _DYNAMIC
+ since ld.so may use its link-time address. */
+ if ((h->def_regular
+ || h->root.type == bfd_link_hash_defined
+ || h->root.type == bfd_link_hash_defweak)
+ && h->type != STT_GNU_IFUNC
&& h != htab->elf.hdynamic
&& SYMBOL_REFERENCES_LOCAL (link_info, h))
{
+ /* bfd_link_hash_new or bfd_link_hash_undefined is
+ set by an assignment in a linker script in
+ bfd_elf_record_link_assignment. FIXME: If we
+ ever get a linker error due relocation overflow,
+ we will skip this optimization. */
+ if (h->def_regular
+ && (h->root.type == bfd_link_hash_new
+ || h->root.type == bfd_link_hash_undefined))
+ goto convert;
tsec = h->root.u.def.section;
toff = h->root.u.def.value;
symtype = h->type;
access is presumed to be an offset from "sym"; The
location of interest is just "sym". */
if (symtype == STT_SECTION)
- toff += irel->r_addend;
+ toff += raddend;
toff = _bfd_merged_section_offset (abfd, &tsec,
elf_section_data (tsec)->sec_info,
toff);
if (symtype != STT_SECTION)
- toff += irel->r_addend;
+ toff += raddend;
}
else
- toff += irel->r_addend;
+ toff += raddend;
/* Don't convert if R_X86_64_PC32 relocation overflows. */
if (tsec->output_section == sec->output_section)
}
}
- /* Don't convert R_X86_64_GOTPCREL if TSEC isn't placed after
- SEC. */
+ /* Don't convert GOTPCREL relocations if TSEC isn't placed
+ after SEC. */
if (asect == NULL)
continue;
continue;
}
- bfd_put_8 (abfd, 0x8d, contents + roff - 2);
- irel->r_info = htab->r_info (r_symndx, R_X86_64_PC32);
+convert:
+ if (opcode == 0xff)
+ {
+ /* We have "call/jmp *foo@GOTPCREL(%rip)". */
+ unsigned int nop;
+ unsigned int disp;
+ bfd_vma nop_offset;
+
+ /* Convert R_X86_64_GOTPCRELX and R_X86_64_REX_GOTPCRELX to
+ R_X86_64_PC32. */
+ modrm = bfd_get_8 (abfd, contents + roff - 1);
+ if (modrm == 0x25)
+ {
+ /* Convert to "jmp foo nop". */
+ modrm = 0xe9;
+ nop = NOP_OPCODE;
+ nop_offset = irel->r_offset + 3;
+ disp = bfd_get_32 (abfd, contents + irel->r_offset);
+ irel->r_offset -= 1;
+ bfd_put_32 (abfd, disp, contents + irel->r_offset);
+ }
+ else
+ {
+ /* Convert to "nop call foo". ADDR_PREFIX_OPCODE
+ is a nop prefix. */
+ modrm = 0xe8;
+ nop = link_info->call_nop_byte;
+ if (link_info->call_nop_as_suffix)
+ {
+ nop_offset = irel->r_offset + 3;
+ disp = bfd_get_32 (abfd, contents + irel->r_offset);
+ irel->r_offset -= 1;
+ bfd_put_32 (abfd, disp, contents + irel->r_offset);
+ }
+ else
+ nop_offset = irel->r_offset - 2;
+ }
+ bfd_put_8 (abfd, nop, contents + nop_offset);
+ bfd_put_8 (abfd, modrm, contents + irel->r_offset - 1);
+ r_type = R_X86_64_PC32;
+ }
+ else
+ {
+ if (opcode == 0x8b)
+ {
+ /* Convert "mov foo@GOTPCREL(%rip), %reg" to
+ "lea foo(%rip), %reg". */
+ opcode = 0x8d;
+ r_type = R_X86_64_PC32;
+ }
+ else
+ {
+ modrm = bfd_get_8 (abfd, contents + roff - 1);
+ if (opcode == 0x85)
+ {
+ /* Convert "test %reg, foo@GOTPCREL(%rip)" to
+ "test $foo, %reg". */
+ modrm = 0xc0 | (modrm & 0x38) >> 3;
+ opcode = 0xf7;
+ }
+ else
+ {
+ /* Convert "binop foo@GOTPCREL(%rip), %reg" to
+ "binop $foo, %reg". */
+ modrm = 0xc0 | (modrm & 0x38) >> 3 | (opcode & 0x3c);
+ opcode = 0x81;
+ }
+ bfd_put_8 (abfd, modrm, contents + roff - 1);
+
+ if (r_type == R_X86_64_REX_GOTPCRELX)
+ {
+ /* Move the R bit to the B bit in REX byte. */
+ unsigned int rex = bfd_get_8 (abfd, contents + roff - 3);
+ rex = (rex & ~REX_R) | (rex & REX_R) >> 2;
+ bfd_put_8 (abfd, rex, contents + roff - 3);
+ }
+ /* No addend for R_X86_64_32S relocation. */
+ irel->r_addend = 0;
+ r_type = R_X86_64_32S;
+ }
+
+ bfd_put_8 (abfd, opcode, contents + roff - 2);
+ }
+
+ irel->r_info = htab->r_info (r_symndx, r_type);
changed_contents = TRUE;
changed_relocs = TRUE;
{
struct elf_dyn_relocs *p;
- if (!elf_x86_64_convert_mov_to_lea (ibfd, s, info))
+ if (!elf_x86_64_convert_load (ibfd, s, info))
return FALSE;
for (p = (struct elf_dyn_relocs *)
bfd_vma *local_got_offsets;
bfd_vma *local_tlsdesc_gotents;
Elf_Internal_Rela *rel;
+ Elf_Internal_Rela *wrel;
Elf_Internal_Rela *relend;
const unsigned int plt_entry_size = GET_PLT_ENTRY_SIZE (info->output_bfd);
elf_x86_64_set_tls_module_base (info);
- rel = relocs;
+ rel = wrel = relocs;
relend = relocs + input_section->reloc_count;
- for (; rel < relend; rel++)
+ for (; rel < relend; wrel++, rel++)
{
unsigned int r_type;
reloc_howto_type *howto;
r_type = ELF32_R_TYPE (rel->r_info);
if (r_type == (int) R_X86_64_GNU_VTINHERIT
|| r_type == (int) R_X86_64_GNU_VTENTRY)
- continue;
+ {
+ if (wrel != rel)
+ *wrel = *rel;
+ continue;
+ }
if (r_type >= (int) R_X86_64_standard)
{
}
if (sec != NULL && discarded_section (sec))
- RELOC_AGAINST_DISCARDED_SECTION (info, input_bfd, input_section,
- rel, 1, relend, howto, 0, contents);
+ {
+ _bfd_clear_contents (howto, input_bfd, input_section,
+ contents + rel->r_offset);
+ wrel->r_offset = rel->r_offset;
+ wrel->r_info = 0;
+ wrel->r_addend = 0;
+
+ /* For ld -r, remove relocations in debug sections against
+ sections defined in discarded sections. Not done for
+ eh_frame editing code expects to be present. */
+ if (bfd_link_relocatable (info)
+ && (input_section->flags & SEC_DEBUGGING))
+ wrel--;
+
+ continue;
+ }
if (bfd_link_relocatable (info))
- continue;
+ {
+ if (wrel != rel)
+ *wrel = *rel;
+ continue;
+ }
if (rel->r_addend == 0 && !ABI_64_P (output_bfd))
{
goto do_relocation;
case R_X86_64_GOTPCREL:
+ case R_X86_64_GOTPCRELX:
+ case R_X86_64_REX_GOTPCRELX:
case R_X86_64_GOTPCREL64:
base_got = htab->elf.sgot;
off = h->got.offset;
/* Relocation is to the entry for this symbol in the global
offset table. */
case R_X86_64_GOTPCREL:
+ case R_X86_64_GOTPCRELX:
+ case R_X86_64_REX_GOTPCRELX:
case R_X86_64_GOTPCREL64:
/* Use global offset table entry as symbol value. */
case R_X86_64_GOTPLT64:
relocation = base_got->output_section->vma
+ base_got->output_offset + off;
- if (r_type != R_X86_64_GOTPCREL && r_type != R_X86_64_GOTPCREL64)
+ if (r_type != R_X86_64_GOTPCREL
+ && r_type != R_X86_64_GOTPCRELX
+ && r_type != R_X86_64_REX_GOTPCRELX
+ && r_type != R_X86_64_GOTPCREL64)
relocation -= htab->elf.sgotplt->output_section->vma
- htab->elf.sgotplt->output_offset;
contents + roff + 8 + largepic);
/* Skip R_X86_64_PC32/R_X86_64_PLT32/R_X86_64_PLTOFF64. */
rel++;
+ wrel++;
continue;
}
else if (ELF32_R_TYPE (rel->r_info) == R_X86_64_GOTPC32_TLSDESC)
contents + roff + 8 + largepic);
/* Skip R_X86_64_PLT32/R_X86_64_PLTOFF64. */
rel++;
+ wrel++;
continue;
}
else if (ELF32_R_TYPE (rel->r_info) == R_X86_64_GOTPC32_TLSDESC)
"\x0f\x1f\x40\x00\x64\x8b\x04\x25\0\0\0", 12);
/* Skip R_X86_64_PC32/R_X86_64_PLT32/R_X86_64_PLTOFF64. */
rel++;
+ wrel++;
continue;
}
return FALSE;
}
}
+
+ if (wrel != rel)
+ *wrel = *rel;
+ }
+
+ if (wrel != rel)
+ {
+ Elf_Internal_Shdr *rel_hdr;
+ size_t deleted = rel - wrel;
+
+ rel_hdr = _bfd_elf_single_rel_hdr (input_section->output_section);
+ rel_hdr->sh_size -= rel_hdr->sh_entsize * deleted;
+ if (rel_hdr->sh_size == 0)
+ {
+ /* It is too late to remove an empty reloc section. Leave
+ one NONE reloc.
+ ??? What is wrong with an empty section??? */
+ rel_hdr->sh_size = rel_hdr->sh_entsize;
+ deleted -= 1;
+ }
+ rel_hdr = _bfd_elf_single_rel_hdr (input_section);
+ rel_hdr->sh_size -= rel_hdr->sh_entsize * deleted;
+ input_section->reloc_count -= deleted;
}
return TRUE;
bfd *abfd = info->output_bfd;
const struct elf_backend_data *bed = get_elf_backend_data (abfd);
struct elf_x86_64_link_hash_table *htab = elf_x86_64_hash_table (info);
- unsigned long r_symndx = htab->r_sym (rela->r_info);
- Elf_Internal_Sym sym;
-
- if (htab->elf.dynsym == NULL
- || !bed->s->swap_symbol_in (abfd,
- (htab->elf.dynsym->contents
- + r_symndx * bed->s->sizeof_sym),
- 0, &sym))
- abort ();
- /* Check relocation against STT_GNU_IFUNC symbol. */
- if (ELF_ST_TYPE (sym.st_info) == STT_GNU_IFUNC)
- return reloc_class_ifunc;
+ if (htab->elf.dynsym != NULL
+ && htab->elf.dynsym->contents != NULL)
+ {
+ /* Check relocation against STT_GNU_IFUNC symbol if there are
+ dynamic symbols. */
+ unsigned long r_symndx = htab->r_sym (rela->r_info);
+ Elf_Internal_Sym sym;
+ if (!bed->s->swap_symbol_in (abfd,
+ (htab->elf.dynsym->contents
+ + r_symndx * bed->s->sizeof_sym),
+ 0, &sym))
+ abort ();
+
+ if (ELF_ST_TYPE (sym.st_info) == STT_GNU_IFUNC)
+ return reloc_class_ifunc;
+ }
switch ((int) ELF32_R_TYPE (rela->r_info))
{
reloc_index = H_GET_32 (abfd, (plt_contents + plt_offset
+ bed->plt_reloc_offset));
- if (reloc_index >= count)
- abort ();
- if (plt_bnd)
+ if (reloc_index < count)
{
- /* This is the index in .plt section. */
- long plt_index = plt_offset / bed->plt_entry_size;
- /* Store VMA + the offset in .plt.bnd section. */
- plt_sym_val[reloc_index] =
- (plt_bnd->vma
- + (plt_index - 1) * sizeof (elf_x86_64_legacy_plt2_entry));
+ if (plt_bnd)
+ {
+ /* This is the index in .plt section. */
+ long plt_index = plt_offset / bed->plt_entry_size;
+ /* Store VMA + the offset in .plt.bnd section. */
+ plt_sym_val[reloc_index] =
+ (plt_bnd->vma
+ + (plt_index - 1) * sizeof (elf_x86_64_legacy_plt2_entry));
+ }
+ else
+ plt_sym_val[reloc_index] = plt->vma + plt_offset;
}
- else
- plt_sym_val[reloc_index] = plt->vma + plt_offset;
plt_offset += bed->plt_entry_size;
/* PR binutils/18437: Skip extra relocations in the .rela.plt