Better overflow checking for powerpc32 relocations
[binutils-gdb.git] / bfd / elf32-ppc.c
index f356c959fd6bbfe67850734579ff096330e3cb71..750aa5e7bc6cbfcaec48a150776fc8aa949b154a 100644 (file)
@@ -1,7 +1,5 @@
 /* PowerPC-specific support for 32-bit ELF
-   Copyright 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
-   2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
-   Free Software Foundation, Inc.
+   Copyright (C) 1994-2014 Free Software Foundation, Inc.
    Written by Ian Lance Taylor, Cygnus Support.
 
    This file is part of BFD, the Binary File Descriptor library.
@@ -52,8 +50,6 @@ static bfd_reloc_status_type ppc_elf_addr16_ha_reloc
   (bfd *, arelent *, asymbol *, void *, asection *, bfd *, char **);
 static bfd_reloc_status_type ppc_elf_unhandled_reloc
   (bfd *, arelent *, asymbol *, void *, asection *, bfd *, char **);
-static void ppc_elf_vle_split16
-  (bfd *, bfd_byte *, bfd_vma, bfd_vma, split16_format_type);
 
 /* Branch prediction bit for branch taken relocs.  */
 #define BRANCH_PREDICT_BIT 0x200000
@@ -194,7 +190,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         32,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_NONE",          /* name */
         FALSE,                 /* partial_inplace */
@@ -209,7 +205,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         32,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_ADDR32",        /* name */
         FALSE,                 /* partial_inplace */
@@ -225,7 +221,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         26,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_signed, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_ADDR24",        /* name */
         FALSE,                 /* partial_inplace */
@@ -302,7 +298,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_signed, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_ADDR14",        /* name */
         FALSE,                 /* partial_inplace */
@@ -319,7 +315,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_signed, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_ADDR14_BRTAKEN",/* name */
         FALSE,                 /* partial_inplace */
@@ -336,7 +332,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_signed, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_ADDR14_BRNTAKEN",/* name */
         FALSE,                 /* partial_inplace */
@@ -448,7 +444,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_GOT16_HI",      /* name */
         FALSE,                 /* partial_inplace */
@@ -464,7 +460,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         ppc_elf_addr16_ha_reloc, /* special_function */
         "R_PPC_GOT16_HA",      /* name */
         FALSE,                 /* partial_inplace */
@@ -499,7 +495,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         32,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc,  /* special_function */
         "R_PPC_COPY",          /* name */
         FALSE,                 /* partial_inplace */
@@ -515,7 +511,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         32,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc,  /* special_function */
         "R_PPC_GLOB_DAT",      /* name */
         FALSE,                 /* partial_inplace */
@@ -530,7 +526,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         32,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc,  /* special_function */
         "R_PPC_JMP_SLOT",      /* name */
         FALSE,                 /* partial_inplace */
@@ -547,7 +543,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         32,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc,  /* special_function */
         "R_PPC_RELATIVE",      /* name */
         FALSE,                 /* partial_inplace */
@@ -579,7 +575,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         32,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_UADDR32",       /* name */
         FALSE,                 /* partial_inplace */
@@ -609,7 +605,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         32,                    /* bitsize */
         TRUE,                  /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_REL32",         /* name */
         FALSE,                 /* partial_inplace */
@@ -625,7 +621,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         32,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_PLT32",         /* name */
         FALSE,                 /* partial_inplace */
@@ -641,7 +637,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         32,                    /* bitsize */
         TRUE,                  /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_PLTREL32",      /* name */
         FALSE,                 /* partial_inplace */
@@ -673,7 +669,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_PLT16_HI",      /* name */
         FALSE,                 /* partial_inplace */
@@ -689,7 +685,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         ppc_elf_addr16_ha_reloc, /* special_function */
         "R_PPC_PLT16_HA",      /* name */
         FALSE,                 /* partial_inplace */
@@ -720,7 +716,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_signed, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_SECTOFF",       /* name */
         FALSE,                 /* partial_inplace */
@@ -750,7 +746,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_SECTOFF_HI",    /* name */
         FALSE,                 /* partial_inplace */
@@ -765,7 +761,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         ppc_elf_addr16_ha_reloc, /* special_function */
         "R_PPC_SECTOFF_HA",    /* name */
         FALSE,                 /* partial_inplace */
@@ -1241,7 +1237,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         32,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_EMB_NADDR32",   /* name */
         FALSE,                 /* partial_inplace */
@@ -1256,7 +1252,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_signed, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_EMB_NADDR16",   /* name */
         FALSE,                 /* partial_inplace */
@@ -1451,10 +1447,10 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
   HOWTO (R_PPC_VLE_LO16A,      /* type */
         0,                     /* rightshift */
         2,                     /* size (0 = byte, 1 = short, 2 = long) */
-        32,                    /* bitsize */
+        16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc,  /* special_function */
         "R_PPC_VLE_LO16A",     /* name */
         FALSE,                 /* partial_inplace */
@@ -1466,10 +1462,10 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
   HOWTO (R_PPC_VLE_LO16D,      /* type */
         0,                     /* rightshift */
         2,                     /* size (0 = byte, 1 = short, 2 = long) */
-        32,                    /* bitsize */
+        16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc,  /* special_function */
         "R_PPC_VLE_LO16D",             /* name */
         FALSE,                 /* partial_inplace */
@@ -1479,12 +1475,12 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
 
   /* Bits 16-31 split16a format.  */
   HOWTO (R_PPC_VLE_HI16A,      /* type */
-        0,                     /* rightshift */
+        16,                    /* rightshift */
         2,                     /* size (0 = byte, 1 = short, 2 = long) */
-        32,                    /* bitsize */
+        16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc,  /* special_function */
         "R_PPC_VLE_HI16A",             /* name */
         FALSE,                 /* partial_inplace */
@@ -1494,12 +1490,12 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
 
   /* Bits 16-31 split16d format.  */
   HOWTO (R_PPC_VLE_HI16D,      /* type */
-        0,                     /* rightshift */
+        16,                    /* rightshift */
         2,                     /* size (0 = byte, 1 = short, 2 = long) */
-        32,                    /* bitsize */
+        16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc,  /* special_function */
         "R_PPC_VLE_HI16D",             /* name */
         FALSE,                 /* partial_inplace */
@@ -1509,12 +1505,12 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
 
   /* Bits 16-31 (High Adjusted) in split16a format.  */
   HOWTO (R_PPC_VLE_HA16A,      /* type */
-        0,                     /* rightshift */
+        16,                    /* rightshift */
         2,                     /* size (0 = byte, 1 = short, 2 = long) */
-        32,                    /* bitsize */
+        16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc,  /* special_function */
         "R_PPC_VLE_HA16A",             /* name */
         FALSE,                 /* partial_inplace */
@@ -1524,12 +1520,12 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
 
   /* Bits 16-31 (High Adjusted) in split16d format.  */
   HOWTO (R_PPC_VLE_HA16D,      /* type */
-        0,                     /* rightshift */
+        16,                    /* rightshift */
         2,                     /* size (0 = byte, 1 = short, 2 = long) */
-        32,                    /* bitsize */
+        16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc,  /* special_function */
         "R_PPC_VLE_HA16D",             /* name */
         FALSE,                 /* partial_inplace */
@@ -1537,14 +1533,16 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         0x1f07ff,              /* dst_mask */
         FALSE),                /* pcrel_offset */
 
-  /* This reloc does nothing.  */
-  HOWTO (R_PPC_VLE_SDA21,              /* type */
+  /* This reloc is like R_PPC_EMB_SDA21 but only applies to e_add16i
+     instructions.  If the register base is 0 then the linker changes
+     the e_add16i to an e_li instruction.  */
+  HOWTO (R_PPC_VLE_SDA21,      /* type */
         0,                     /* rightshift */
         2,                     /* size (0 = byte, 1 = short, 2 = long) */
-        32,                    /* bitsize */
+        16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_signed, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_VLE_SDA21",             /* name */
         FALSE,                 /* partial_inplace */
@@ -1552,29 +1550,29 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         0xffff,                /* dst_mask */
         FALSE),                /* pcrel_offset */
 
-  /* This reloc does nothing.  */
+  /* Like R_PPC_VLE_SDA21 but ignore overflow.  */
   HOWTO (R_PPC_VLE_SDA21_LO,   /* type */
         0,                     /* rightshift */
         2,                     /* size (0 = byte, 1 = short, 2 = long) */
-        32,                    /* bitsize */
+        16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_VLE_SDA21_LO",  /* name */
         FALSE,                 /* partial_inplace */
         0,                     /* src_mask */
-        0,                     /* dst_mask */
+        0xffff,                /* dst_mask */
         FALSE),                /* pcrel_offset */
 
   /* The 16 LSBS relative to _SDA_BASE_ in split16a format.  */
   HOWTO (R_PPC_VLE_SDAREL_LO16A,/* type */
         0,                     /* rightshift */
         2,                     /* size (0 = byte, 1 = short, 2 = long) */
-        32,                    /* bitsize */
+        16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield,    /* complain_on_overflow */
+        complain_overflow_dont,        /* complain_on_overflow */
         bfd_elf_generic_reloc,  /* special_function */
         "R_PPC_VLE_SDAREL_LO16A",      /* name */
         FALSE,                 /* partial_inplace */
@@ -1583,14 +1581,13 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         FALSE),                /* pcrel_offset */
 
   /* The 16 LSBS relative to _SDA_BASE_ in split16d format.  */
-  /* This reloc does nothing.  */
   HOWTO (R_PPC_VLE_SDAREL_LO16D, /* type */
         0,                     /* rightshift */
         2,                     /* size (0 = byte, 1 = short, 2 = long) */
-        32,                    /* bitsize */
+        16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield,    /* complain_on_overflow */
+        complain_overflow_dont,        /* complain_on_overflow */
         bfd_elf_generic_reloc,  /* special_function */
         "R_PPC_VLE_SDAREL_LO16D",              /* name */
         FALSE,                 /* partial_inplace */
@@ -1600,12 +1597,12 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
 
   /* Bits 16-31 relative to _SDA_BASE_ in split16a format.  */
   HOWTO (R_PPC_VLE_SDAREL_HI16A,       /* type */
-        0,                     /* rightshift */
+        16,                    /* rightshift */
         2,                     /* size (0 = byte, 1 = short, 2 = long) */
-        32,                    /* bitsize */
+        16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield,    /* complain_on_overflow */
+        complain_overflow_dont,        /* complain_on_overflow */
         bfd_elf_generic_reloc,  /* special_function */
         "R_PPC_VLE_SDAREL_HI16A",      /* name */
         FALSE,                 /* partial_inplace */
@@ -1615,12 +1612,12 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
 
   /* Bits 16-31 relative to _SDA_BASE_ in split16d format.  */
   HOWTO (R_PPC_VLE_SDAREL_HI16D,       /* type */
-        0,                     /* rightshift */
+        16,                    /* rightshift */
         2,                     /* size (0 = byte, 1 = short, 2 = long) */
-        32,                    /* bitsize */
+        16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield,    /* complain_on_overflow */
+        complain_overflow_dont,        /* complain_on_overflow */
         bfd_elf_generic_reloc,  /* special_function */
         "R_PPC_VLE_SDAREL_HI16D",      /* name */
         FALSE,                 /* partial_inplace */
@@ -1630,12 +1627,12 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
 
   /* Bits 16-31 (HA) relative to _SDA_BASE split16a format.  */
   HOWTO (R_PPC_VLE_SDAREL_HA16A,       /* type */
-        0,                     /* rightshift */
+        16,                    /* rightshift */
         2,                     /* size (0 = byte, 1 = short, 2 = long) */
-        32,                    /* bitsize */
+        16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield,    /* complain_on_overflow */
+        complain_overflow_dont,        /* complain_on_overflow */
         bfd_elf_generic_reloc,  /* special_function */
         "R_PPC_VLE_SDAREL_HA16A",      /* name */
         FALSE,                 /* partial_inplace */
@@ -1645,12 +1642,12 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
 
   /* Bits 16-31 (HA) relative to _SDA_BASE split16d format.  */
   HOWTO (R_PPC_VLE_SDAREL_HA16D,       /* type */
-        0,                     /* rightshift */
+        16,                    /* rightshift */
         2,                     /* size (0 = byte, 1 = short, 2 = long) */
-        32,                    /* bitsize */
+        16,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield,    /* complain_on_overflow */
+        complain_overflow_dont,        /* complain_on_overflow */
         bfd_elf_generic_reloc,  /* special_function */
         "R_PPC_VLE_SDAREL_HA16D",      /* name */
         FALSE,                 /* partial_inplace */
@@ -1664,7 +1661,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         32,                    /* bitsize */
         FALSE,                 /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_dont, /* complain_on_overflow */
         bfd_elf_generic_reloc,  /* special_function */
         "R_PPC_IRELATIVE",     /* name */
         FALSE,                 /* partial_inplace */
@@ -1679,7 +1676,7 @@ static reloc_howto_type ppc_elf_howto_raw[] = {
         16,                    /* bitsize */
         TRUE,                  /* pc_relative */
         0,                     /* bitpos */
-        complain_overflow_bitfield, /* complain_on_overflow */
+        complain_overflow_signed, /* complain_on_overflow */
         bfd_elf_generic_reloc, /* special_function */
         "R_PPC_REL16",         /* name */
         FALSE,                 /* partial_inplace */
@@ -3139,6 +3136,9 @@ struct ppc_elf_link_hash_table
 {
   struct elf_link_hash_table elf;
 
+  /* Various options passed from the linker.  */
+  struct ppc_elf_params *params;
+
   /* Short-cuts to get to dynamic linker sections.  */
   asection *got;
   asection *relgot;
@@ -3184,12 +3184,6 @@ struct ppc_elf_link_hash_table
   /* The type of PLT we have chosen to use.  */
   enum ppc_elf_plt_type plt_type;
 
-  /* Set if we should emit symbols for stubs.  */
-  unsigned int emit_stub_syms:1;
-
-  /* Set if __tls_get_addr optimization should not be done.  */
-  unsigned int no_tls_get_addr_opt:1;
-
   /* True if the target system is VxWorks.  */
   unsigned int is_vxworks:1;
 
@@ -3255,6 +3249,7 @@ static struct bfd_link_hash_table *
 ppc_elf_link_hash_table_create (bfd *abfd)
 {
   struct ppc_elf_link_hash_table *ret;
+  static struct ppc_elf_params default_params = { PLT_OLD, 0, 1, 0, 0, 4096 };
 
   ret = bfd_zmalloc (sizeof (struct ppc_elf_link_hash_table));
   if (ret == NULL)
@@ -3274,6 +3269,8 @@ ppc_elf_link_hash_table_create (bfd *abfd)
   ret->elf.init_plt_offset.offset = 0;
   ret->elf.init_plt_offset.glist = NULL;
 
+  ret->params = &default_params;
+
   ret->sdata[0].name = ".sdata";
   ret->sdata[0].sym_name = "_SDA_BASE_";
   ret->sdata[0].bss_name = ".sbss";
@@ -3289,6 +3286,17 @@ ppc_elf_link_hash_table_create (bfd *abfd)
   return &ret->elf.root;
 }
 
+/* Hook linker params into hash table.  */
+
+void
+ppc_elf_link_params (struct bfd_link_info *info, struct ppc_elf_params *params)
+{
+  struct ppc_elf_link_hash_table *htab = ppc_elf_hash_table (info);
+
+  if (htab)
+    htab->params = params;
+}
+
 /* Create .got and the related sections.  */
 
 static bfd_boolean
@@ -3341,7 +3349,8 @@ ppc_elf_create_glink (bfd *abfd, struct bfd_link_info *info)
   s = bfd_make_section_anyway_with_flags (abfd, ".glink", flags);
   htab->glink = s;
   if (s == NULL
-      || !bfd_set_section_alignment (abfd, s, 4))
+      || !bfd_set_section_alignment (abfd, s,
+                                    htab->params->ppc476_workaround ? 6 : 4))
     return FALSE;
 
   if (!info->no_ld_generated_unwind_info)
@@ -4283,6 +4292,20 @@ ppc_elf_check_relocs (bfd *abfd,
              htab->plt_type = PLT_OLD;
              htab->old_bfd = abfd;
            }
+         if (h != NULL && h->type == STT_GNU_IFUNC)
+           {
+             if (info->shared)
+               {
+                 info->callbacks->einfo (_("%P: %H: @local call to ifunc %s\n"),
+                                         abfd, sec, rel->r_offset,
+                                         h->root.root.string);
+                 bfd_set_error (bfd_error_bad_value);
+                 return FALSE;
+               }
+             h->needs_plt = 1;
+             if (!update_plt_info (abfd, &h->plt.plist, NULL, 0))
+               return FALSE;
+           }
          break;
 
          /* This relocation describes the C++ object vtable hierarchy.
@@ -4424,7 +4447,7 @@ ppc_elf_check_relocs (bfd *abfd,
          if ((info->shared
               && (must_be_dyn_reloc (info, r_type)
                   || (h != NULL
-                      && (! info->symbolic
+                      && (!SYMBOLIC_BIND (info, h)
                           || h->root.type == bfd_link_hash_defweak
                           || !h->def_regular))))
              || (ELIMINATE_COPY_RELOCS
@@ -4766,20 +4789,19 @@ ppc_elf_merge_private_bfd_data (bfd *ibfd, bfd *obfd)
 }
 
 static void
-ppc_elf_vle_split16 (bfd *output_bfd, bfd_byte *contents,
-                 bfd_vma offset, bfd_vma relocation,
-                split16_format_type split16_format)
+ppc_elf_vle_split16 (bfd *output_bfd, bfd_byte *loc,
+                    bfd_vma value,
+                    split16_format_type split16_format)
 
 {
-  bfd_vma insn, top5, bottom11;
+  unsigned int insn, top5;
 
-  insn = bfd_get_32 (output_bfd, contents + offset);
-  top5 = relocation >> 11;
-  top5 = top5 << (split16_format == split16a_type ? 20 : 16);
-  bottom11 = relocation & 0x7ff;
+  insn = bfd_get_32 (output_bfd, loc);
+  top5 = value & 0xf800;
+  top5 = top5 << (split16_format == split16a_type ? 9 : 5);
   insn |= top5;
-  insn |= bottom11;
-  bfd_put_32 (output_bfd, insn, contents + offset);
+  insn |= value & 0x7ff;
+  bfd_put_32 (output_bfd, insn, loc);
 }
 
 \f
@@ -4787,22 +4809,18 @@ ppc_elf_vle_split16 (bfd *output_bfd, bfd_byte *contents,
    Returns -1 on error, 0 for old PLT, 1 for new PLT.  */
 int
 ppc_elf_select_plt_layout (bfd *output_bfd ATTRIBUTE_UNUSED,
-                          struct bfd_link_info *info,
-                          enum ppc_elf_plt_type plt_style,
-                          int emit_stub_syms)
+                          struct bfd_link_info *info)
 {
   struct ppc_elf_link_hash_table *htab;
   flagword flags;
 
   htab = ppc_elf_hash_table (info);
 
-  htab->emit_stub_syms = emit_stub_syms;
-
   if (htab->plt_type == PLT_UNSET)
     {
       struct elf_link_hash_entry *h;
 
-      if (plt_style == PLT_OLD)
+      if (htab->params->plt_style == PLT_OLD)
        htab->plt_type = PLT_OLD;
       else if (info->shared
               && htab->elf.dynamic_sections_created
@@ -4824,7 +4842,7 @@ ppc_elf_select_plt_layout (bfd *output_bfd ATTRIBUTE_UNUSED,
       else
        {
          bfd *ibfd;
-         enum ppc_elf_plt_type plt_type = plt_style;
+         enum ppc_elf_plt_type plt_type = htab->params->plt_style;
 
          /* Look through the reloc flags left by ppc_elf_check_relocs.
             Use the old style bss plt if a file makes plt calls
@@ -4847,7 +4865,7 @@ ppc_elf_select_plt_layout (bfd *output_bfd ATTRIBUTE_UNUSED,
          htab->plt_type = plt_type;
        }
     }
-  if (htab->plt_type == PLT_OLD && plt_style == PLT_NEW)
+  if (htab->plt_type == PLT_OLD && htab->params->plt_style == PLT_NEW)
     {
       if (htab->old_bfd != NULL)
        info->callbacks->einfo (_("%P: bss-plt forced due to %B\n"),
@@ -5083,16 +5101,14 @@ ppc_elf_gc_sweep_hook (bfd *abfd,
    generic ELF tls_setup function.  */
 
 asection *
-ppc_elf_tls_setup (bfd *obfd,
-                  struct bfd_link_info *info,
-                  int no_tls_get_addr_opt)
+ppc_elf_tls_setup (bfd *obfd, struct bfd_link_info *info)
 {
   struct ppc_elf_link_hash_table *htab;
 
   htab = ppc_elf_hash_table (info);
   htab->tls_get_addr = elf_link_hash_lookup (&htab->elf, "__tls_get_addr",
                                             FALSE, FALSE, TRUE);
-  if (!no_tls_get_addr_opt)
+  if (!htab->params->no_tls_get_addr_opt)
     {
       struct elf_link_hash_entry *opt, *tga;
       opt = elf_link_hash_lookup (&htab->elf, "__tls_get_addr_opt",
@@ -5137,9 +5153,8 @@ ppc_elf_tls_setup (bfd *obfd,
            }
        }
       else
-       no_tls_get_addr_opt = TRUE;
+       htab->params->no_tls_get_addr_opt = TRUE;
     }
-  htab->no_tls_get_addr_opt = no_tls_get_addr_opt;
   if (htab->plt_type == PLT_NEW
       && htab->plt != NULL
       && htab->plt->output_section != NULL)
@@ -5770,7 +5785,7 @@ allocate_dynrelocs (struct elf_link_hash_entry *h, void *inf)
                        glink_offset = s->size;
                        s->size += GLINK_ENTRY_SIZE;
                        if (h == htab->tls_get_addr
-                           && !htab->no_tls_get_addr_opt)
+                           && !htab->params->no_tls_get_addr_opt)
                          s->size += TLS_GET_ADDR_GLINK_SIZE - GLINK_ENTRY_SIZE;
                      }
                    if (!doneone
@@ -5783,7 +5798,7 @@ allocate_dynrelocs (struct elf_link_hash_entry *h, void *inf)
                      }
                    ent->glink_offset = glink_offset;
 
-                   if (htab->emit_stub_syms
+                   if (htab->params->emit_stub_syms
                        && !add_stub_sym (ent, h, info))
                      return FALSE;
                  }
@@ -6325,10 +6340,11 @@ ppc_elf_size_dynamic_sections (bfd *output_bfd ATTRIBUTE_UNUSED,
       /* Space for the branch table.  */
       htab->glink->size += htab->glink->size / (GLINK_ENTRY_SIZE / 4) - 4;
       /* Pad out to align the start of PLTresolve.  */
-      htab->glink->size += -htab->glink->size & 15;
+      htab->glink->size += -htab->glink->size & (htab->params->ppc476_workaround
+                                                ? 63 : 15);
       htab->glink->size += GLINK_PLTRESOLVE;
 
-      if (htab->emit_stub_syms)
+      if (htab->params->emit_stub_syms)
        {
          struct elf_link_hash_entry *sh;
          sh = elf_link_hash_lookup (&htab->elf, "__glink",
@@ -6481,14 +6497,16 @@ ppc_elf_size_dynamic_sections (bfd *output_bfd ATTRIBUTE_UNUSED,
            return FALSE;
        }
 
-      if (htab->glink != NULL && htab->glink->size != 0)
+      if (htab->plt_type == PLT_NEW
+         && htab->glink != NULL
+         && htab->glink->size != 0)
        {
          if (!add_dynamic_entry (DT_PPC_GOT, 0))
            return FALSE;
-         if (!htab->no_tls_get_addr_opt
+         if (!htab->params->no_tls_get_addr_opt
              && htab->tls_get_addr != NULL
              && htab->tls_get_addr->plt.plist != NULL
-             && !add_dynamic_entry (DT_PPC_TLSOPT, 0))
+             && !add_dynamic_entry (DT_PPC_OPT, PPC_OPT_TLS))
            return FALSE;
        }
 
@@ -6604,7 +6622,7 @@ static const int shared_stub_entry[] =
     0x429f0005, /* bcl 20, 31, .Lxxx */
     0x7d8802a6, /* mflr 12 */
     0x3d8c0000, /* addis 12, 12, (xxx-.Lxxx)@ha */
-    0x398c0008, /* addi 12, 12, (xxx-.Lxxx)@l */
+    0x398c0000, /* addi 12, 12, (xxx-.Lxxx)@l */
     0x7c0803a6, /* mtlr 0 */
     0x7d8903a6, /* mtctr 12 */
     0x4e800420, /* bctr */
@@ -6618,6 +6636,16 @@ static const int stub_entry[] =
     0x4e800420, /* bctr */
   };
 
+struct ppc_elf_relax_info
+{
+  unsigned int workaround_size;
+};
+
+/* This function implements long branch trampolines, and the ppc476
+   icache bug workaround.  Any section needing trampolines or patch
+   space for the workaround has its size extended so that we can
+   add trampolines at the end of the section.  */
+
 static bfd_boolean
 ppc_elf_relax_section (bfd *abfd,
                       asection *isec,
@@ -6638,408 +6666,488 @@ ppc_elf_relax_section (bfd *abfd,
   bfd_byte *contents = NULL;
   Elf_Internal_Sym *isymbuf = NULL;
   Elf_Internal_Rela *internal_relocs = NULL;
-  Elf_Internal_Rela *irel, *irelend;
+  Elf_Internal_Rela *irel, *irelend = NULL;
   struct one_fixup *fixups = NULL;
+  struct ppc_elf_relax_info *relax_info = NULL;
   unsigned changes = 0;
+  bfd_boolean workaround_change;
   struct ppc_elf_link_hash_table *htab;
-  bfd_size_type trampoff;
+  bfd_size_type trampbase, trampoff, newsize;
   asection *got2;
   bfd_boolean maybe_pasted;
 
   *again = FALSE;
 
-  /* Nothing to do if there are no relocations, and no need to do
-     anything with non-alloc or non-code sections.  */
+  /* No need to do anything with non-alloc or non-code sections.  */
   if ((isec->flags & SEC_ALLOC) == 0
       || (isec->flags & SEC_CODE) == 0
-      || (isec->flags & SEC_RELOC) == 0
-      || isec->reloc_count == 0)
+      || (isec->flags & SEC_LINKER_CREATED) != 0
+      || isec->size < 4)
     return TRUE;
 
   /* We cannot represent the required PIC relocs in the output, so don't
      do anything.  The linker doesn't support mixing -shared and -r
      anyway.  */
   if (link_info->relocatable && link_info->shared)
-     return TRUE;
+    return TRUE;
+
+  htab = ppc_elf_hash_table (link_info);
+  if (htab == NULL)
+    return TRUE;
+
+  isec->size = (isec->size + 3) & -4;
+  if (isec->rawsize == 0)
+    isec->rawsize = isec->size;
+  trampbase = isec->size;
+
+  BFD_ASSERT (isec->sec_info_type == SEC_INFO_TYPE_NONE
+             || isec->sec_info_type == SEC_INFO_TYPE_TARGET);
+  isec->sec_info_type = SEC_INFO_TYPE_TARGET;
+
+  if (htab->params->ppc476_workaround)
+    {
+      if (elf_section_data (isec)->sec_info == NULL)
+       {
+         elf_section_data (isec)->sec_info
+           = bfd_zalloc (abfd, sizeof (struct ppc_elf_relax_info));
+         if (elf_section_data (isec)->sec_info == NULL)
+           return FALSE;
+       }
+      relax_info = elf_section_data (isec)->sec_info;
+      trampbase -= relax_info->workaround_size;
+    }
 
-  trampoff = (isec->size + 3) & (bfd_vma) -4;
   maybe_pasted = (strcmp (isec->output_section->name, ".init") == 0
                  || strcmp (isec->output_section->name, ".fini") == 0);
   /* Space for a branch around any trampolines.  */
-  if (maybe_pasted)
+  trampoff = trampbase;
+  if (maybe_pasted && trampbase == isec->rawsize)
     trampoff += 4;
 
   symtab_hdr = &elf_symtab_hdr (abfd);
 
-  /* Get a copy of the native relocations.  */
-  internal_relocs = _bfd_elf_link_read_relocs (abfd, isec, NULL, NULL,
-                                              link_info->keep_memory);
-  if (internal_relocs == NULL)
-    goto error_return;
-
-  htab = ppc_elf_hash_table (link_info);
-  got2 = bfd_get_section_by_name (abfd, ".got2");
-
-  irelend = internal_relocs + isec->reloc_count;
-  for (irel = internal_relocs; irel < irelend; irel++)
+  if (htab->params->branch_trampolines)
     {
-      unsigned long r_type = ELF32_R_TYPE (irel->r_info);
-      bfd_vma toff, roff;
-      asection *tsec;
-      struct one_fixup *f;
-      size_t insn_offset = 0;
-      bfd_vma max_branch_offset, val;
-      bfd_byte *hit_addr;
-      unsigned long t0;
-      struct elf_link_hash_entry *h;
-      struct plt_entry **plist;
-      unsigned char sym_type;
-
-      switch (r_type)
+      /* Get a copy of the native relocations.  */
+      if (isec->reloc_count != 0)
        {
-       case R_PPC_REL24:
-       case R_PPC_LOCAL24PC:
-       case R_PPC_PLTREL24:
-         max_branch_offset = 1 << 25;
-         break;
-
-       case R_PPC_REL14:
-       case R_PPC_REL14_BRTAKEN:
-       case R_PPC_REL14_BRNTAKEN:
-         max_branch_offset = 1 << 15;
-         break;
-
-       default:
-         continue;
+         internal_relocs = _bfd_elf_link_read_relocs (abfd, isec, NULL, NULL,
+                                                      link_info->keep_memory);
+         if (internal_relocs == NULL)
+           goto error_return;
        }
 
-      /* Get the value of the symbol referred to by the reloc.  */
-      h = NULL;
-      if (ELF32_R_SYM (irel->r_info) < symtab_hdr->sh_info)
-       {
-         /* A local symbol.  */
-         Elf_Internal_Sym *isym;
+      got2 = bfd_get_section_by_name (abfd, ".got2");
 
-         /* Read this BFD's local symbols.  */
-         if (isymbuf == NULL)
-           {
-             isymbuf = (Elf_Internal_Sym *) symtab_hdr->contents;
-             if (isymbuf == NULL)
-               isymbuf = bfd_elf_get_elf_syms (abfd, symtab_hdr,
-                                               symtab_hdr->sh_info, 0,
-                                               NULL, NULL, NULL);
-             if (isymbuf == 0)
-               goto error_return;
-           }
-         isym = isymbuf + ELF32_R_SYM (irel->r_info);
-         if (isym->st_shndx == SHN_UNDEF)
-           tsec = bfd_und_section_ptr;
-         else if (isym->st_shndx == SHN_ABS)
-           tsec = bfd_abs_section_ptr;
-         else if (isym->st_shndx == SHN_COMMON)
-           tsec = bfd_com_section_ptr;
-         else
-           tsec = bfd_section_from_elf_index (abfd, isym->st_shndx);
-
-         toff = isym->st_value;
-         sym_type = ELF_ST_TYPE (isym->st_info);
-       }
-      else
+      irelend = internal_relocs + isec->reloc_count;
+      for (irel = internal_relocs; irel < irelend; irel++)
        {
-         /* Global symbol handling.  */
-         unsigned long indx;
-
-         indx = ELF32_R_SYM (irel->r_info) - symtab_hdr->sh_info;
-         h = elf_sym_hashes (abfd)[indx];
+         unsigned long r_type = ELF32_R_TYPE (irel->r_info);
+         bfd_vma toff, roff;
+         asection *tsec;
+         struct one_fixup *f;
+         size_t insn_offset = 0;
+         bfd_vma max_branch_offset, val;
+         bfd_byte *hit_addr;
+         unsigned long t0;
+         struct elf_link_hash_entry *h;
+         struct plt_entry **plist;
+         unsigned char sym_type;
+
+         switch (r_type)
+           {
+           case R_PPC_REL24:
+           case R_PPC_LOCAL24PC:
+           case R_PPC_PLTREL24:
+             max_branch_offset = 1 << 25;
+             break;
 
-         while (h->root.type == bfd_link_hash_indirect
-                || h->root.type == bfd_link_hash_warning)
-           h = (struct elf_link_hash_entry *) h->root.u.i.link;
+           case R_PPC_REL14:
+           case R_PPC_REL14_BRTAKEN:
+           case R_PPC_REL14_BRNTAKEN:
+             max_branch_offset = 1 << 15;
+             break;
 
-         if (h->root.type == bfd_link_hash_defined
-             || h->root.type == bfd_link_hash_defweak)
-           {
-             tsec = h->root.u.def.section;
-             toff = h->root.u.def.value;
+           default:
+             continue;
            }
-         else if (h->root.type == bfd_link_hash_undefined
-                  || h->root.type == bfd_link_hash_undefweak)
+
+         /* Get the value of the symbol referred to by the reloc.  */
+         h = NULL;
+         if (ELF32_R_SYM (irel->r_info) < symtab_hdr->sh_info)
            {
-             tsec = bfd_und_section_ptr;
-             toff = link_info->relocatable ? indx : 0;
+             /* A local symbol.  */
+             Elf_Internal_Sym *isym;
+
+             /* Read this BFD's local symbols.  */
+             if (isymbuf == NULL)
+               {
+                 isymbuf = (Elf_Internal_Sym *) symtab_hdr->contents;
+                 if (isymbuf == NULL)
+                   isymbuf = bfd_elf_get_elf_syms (abfd, symtab_hdr,
+                                                   symtab_hdr->sh_info, 0,
+                                                   NULL, NULL, NULL);
+                 if (isymbuf == 0)
+                   goto error_return;
+               }
+             isym = isymbuf + ELF32_R_SYM (irel->r_info);
+             if (isym->st_shndx == SHN_UNDEF)
+               tsec = bfd_und_section_ptr;
+             else if (isym->st_shndx == SHN_ABS)
+               tsec = bfd_abs_section_ptr;
+             else if (isym->st_shndx == SHN_COMMON)
+               tsec = bfd_com_section_ptr;
+             else
+               tsec = bfd_section_from_elf_index (abfd, isym->st_shndx);
+
+             toff = isym->st_value;
+             sym_type = ELF_ST_TYPE (isym->st_info);
            }
          else
-           continue;
+           {
+             /* Global symbol handling.  */
+             unsigned long indx;
 
-         sym_type = h->type;
-       }
+             indx = ELF32_R_SYM (irel->r_info) - symtab_hdr->sh_info;
+             h = elf_sym_hashes (abfd)[indx];
 
-      /* The condition here under which we call find_plt_ent must
-        match that in relocate_section.  If we call find_plt_ent here
-        but not in relocate_section, or vice versa, then the branch
-        destination used here may be incorrect.  */
-      plist = NULL;
-      if (h != NULL)
-       {
-         /* We know is_branch_reloc (r_type) is true.  */
-         if (h->type == STT_GNU_IFUNC
-             || r_type == R_PPC_PLTREL24)
-           plist = &h->plt.plist;
-       }
-      else if (sym_type == STT_GNU_IFUNC
-              && elf_local_got_offsets (abfd) != NULL)
-       {
-         bfd_vma *local_got_offsets = elf_local_got_offsets (abfd);
-         struct plt_entry **local_plt = (struct plt_entry **)
-           (local_got_offsets + symtab_hdr->sh_info);
-         plist = local_plt + ELF32_R_SYM (irel->r_info);
-       }
-      if (plist != NULL)
-       {
-         bfd_vma addend = 0;
-         struct plt_entry *ent;
+             while (h->root.type == bfd_link_hash_indirect
+                    || h->root.type == bfd_link_hash_warning)
+               h = (struct elf_link_hash_entry *) h->root.u.i.link;
 
-         if (r_type == R_PPC_PLTREL24 && link_info->shared)
-           addend = irel->r_addend;
-         ent = find_plt_ent (plist, got2, addend);
-         if (ent != NULL)
-           {
-             if (htab->plt_type == PLT_NEW
-                 || h == NULL
-                 || !htab->elf.dynamic_sections_created
-                 || h->dynindx == -1)
+             if (h->root.type == bfd_link_hash_defined
+                 || h->root.type == bfd_link_hash_defweak)
                {
-                 tsec = htab->glink;
-                 toff = ent->glink_offset;
+                 tsec = h->root.u.def.section;
+                 toff = h->root.u.def.value;
                }
-             else
+             else if (h->root.type == bfd_link_hash_undefined
+                      || h->root.type == bfd_link_hash_undefweak)
                {
-                 tsec = htab->plt;
-                 toff = ent->plt.offset;
+                 tsec = bfd_und_section_ptr;
+                 toff = link_info->relocatable ? indx : 0;
                }
-           }
-       }
-
-      /* If the branch and target are in the same section, you have
-        no hope of adding stubs.  We'll error out later should the
-        branch overflow.  */
-      if (tsec == isec)
-       continue;
-
-      /* There probably isn't any reason to handle symbols in
-        SEC_MERGE sections;  SEC_MERGE doesn't seem a likely
-        attribute for a code section, and we are only looking at
-        branches.  However, implement it correctly here as a
-        reference for other target relax_section functions.  */
-      if (0 && tsec->sec_info_type == SEC_INFO_TYPE_MERGE)
-       {
-         /* At this stage in linking, no SEC_MERGE symbol has been
-            adjusted, so all references to such symbols need to be
-            passed through _bfd_merged_section_offset.  (Later, in
-            relocate_section, all SEC_MERGE symbols *except* for
-            section symbols have been adjusted.)
-
-            gas may reduce relocations against symbols in SEC_MERGE
-            sections to a relocation against the section symbol when
-            the original addend was zero.  When the reloc is against
-            a section symbol we should include the addend in the
-            offset passed to _bfd_merged_section_offset, since the
-            location of interest is the original symbol.  On the
-            other hand, an access to "sym+addend" where "sym" is not
-            a section symbol should not include the addend;  Such an
-            access is presumed to be an offset from "sym";  The
-            location of interest is just "sym".  */
-         if (sym_type == STT_SECTION)
-           toff += irel->r_addend;
+             else
+               continue;
 
-         toff = _bfd_merged_section_offset (abfd, &tsec,
-                                            elf_section_data (tsec)->sec_info,
-                                            toff);
+             /* If this branch is to __tls_get_addr then we may later
+                optimise away the call.  We won't be needing a long-
+                branch stub in that case.  */
+             if (link_info->executable
+                 && !link_info->relocatable
+                 && h == htab->tls_get_addr
+                 && irel != internal_relocs)
+               {
+                 unsigned long t_symndx = ELF32_R_SYM (irel[-1].r_info);
+                 unsigned long t_rtype = ELF32_R_TYPE (irel[-1].r_info);
+                 unsigned int tls_mask = 0;
+
+                 /* The previous reloc should be one of R_PPC_TLSGD or
+                    R_PPC_TLSLD, or for older object files, a reloc
+                    on the __tls_get_addr arg setup insn.  Get tls
+                    mask bits from the symbol on that reloc.  */
+                 if (t_symndx < symtab_hdr->sh_info)
+                   {
+                     bfd_vma *local_got_offsets = elf_local_got_offsets (abfd);
 
-         if (sym_type != STT_SECTION)
-           toff += irel->r_addend;
-       }
-      /* PLTREL24 addends are special.  */
-      else if (r_type != R_PPC_PLTREL24)
-       toff += irel->r_addend;
+                     if (local_got_offsets != NULL)
+                       {
+                         struct plt_entry **local_plt = (struct plt_entry **)
+                           (local_got_offsets + symtab_hdr->sh_info);
+                         char *lgot_masks = (char *)
+                           (local_plt + symtab_hdr->sh_info);
+                         tls_mask = lgot_masks[t_symndx];
+                       }
+                   }
+                 else
+                   {
+                     struct elf_link_hash_entry *th
+                       = elf_sym_hashes (abfd)[t_symndx - symtab_hdr->sh_info];
 
-      /* Attempted -shared link of non-pic code loses.  */
-      if (tsec->output_section == NULL)
-       continue;
+                     while (th->root.type == bfd_link_hash_indirect
+                            || th->root.type == bfd_link_hash_warning)
+                       th = (struct elf_link_hash_entry *) th->root.u.i.link;
 
-      roff = irel->r_offset;
+                     tls_mask
+                       = ((struct ppc_elf_link_hash_entry *) th)->tls_mask;
+                   }
 
-      /* If the branch is in range, no need to do anything.  */
-      if (tsec != bfd_und_section_ptr
-         && (!link_info->relocatable
-             /* A relocatable link may have sections moved during
-                final link, so do not presume they remain in range.  */
-             || tsec->output_section == isec->output_section))
-       {
-         bfd_vma symaddr, reladdr;
+                 /* The mask bits tell us if the call will be
+                    optimised away.  */
+                 if ((tls_mask & TLS_TLS) != 0 && (tls_mask & TLS_GD) == 0
+                     && (t_rtype == R_PPC_TLSGD
+                         || t_rtype == R_PPC_GOT_TLSGD16
+                         || t_rtype == R_PPC_GOT_TLSGD16_LO))
+                   continue;
+                 if ((tls_mask & TLS_TLS) != 0 && (tls_mask & TLS_LD) == 0
+                     && (t_rtype == R_PPC_TLSLD
+                         || t_rtype == R_PPC_GOT_TLSLD16
+                         || t_rtype == R_PPC_GOT_TLSLD16_LO))
+                   continue;
+               }
 
-         symaddr = tsec->output_section->vma + tsec->output_offset + toff;
-         reladdr = isec->output_section->vma + isec->output_offset + roff;
-         if (symaddr - reladdr + max_branch_offset < 2 * max_branch_offset)
-           continue;
-       }
+             sym_type = h->type;
+           }
 
-      /* Look for an existing fixup to this address.  */
-      for (f = fixups; f ; f = f->next)
-       if (f->tsec == tsec && f->toff == toff)
-         break;
+         /* The condition here under which we call find_plt_ent must
+            match that in relocate_section.  If we call find_plt_ent here
+            but not in relocate_section, or vice versa, then the branch
+            destination used here may be incorrect.  */
+         plist = NULL;
+         if (h != NULL)
+           {
+             /* We know is_branch_reloc (r_type) is true.  */
+             if (h->type == STT_GNU_IFUNC
+                 || r_type == R_PPC_PLTREL24)
+               plist = &h->plt.plist;
+           }
+         else if (sym_type == STT_GNU_IFUNC
+                  && elf_local_got_offsets (abfd) != NULL)
+           {
+             bfd_vma *local_got_offsets = elf_local_got_offsets (abfd);
+             struct plt_entry **local_plt = (struct plt_entry **)
+               (local_got_offsets + symtab_hdr->sh_info);
+             plist = local_plt + ELF32_R_SYM (irel->r_info);
+           }
+         if (plist != NULL)
+           {
+             bfd_vma addend = 0;
+             struct plt_entry *ent;
 
-      if (f == NULL)
-       {
-         size_t size;
-         unsigned long stub_rtype;
+             if (r_type == R_PPC_PLTREL24 && link_info->shared)
+               addend = irel->r_addend;
+             ent = find_plt_ent (plist, got2, addend);
+             if (ent != NULL)
+               {
+                 if (htab->plt_type == PLT_NEW
+                     || h == NULL
+                     || !htab->elf.dynamic_sections_created
+                     || h->dynindx == -1)
+                   {
+                     tsec = htab->glink;
+                     toff = ent->glink_offset;
+                   }
+                 else
+                   {
+                     tsec = htab->plt;
+                     toff = ent->plt.offset;
+                   }
+               }
+           }
 
-         val = trampoff - roff;
-         if (val >= max_branch_offset)
-           /* Oh dear, we can't reach a trampoline.  Don't try to add
-              one.  We'll report an error later.  */
+         /* If the branch and target are in the same section, you have
+            no hope of adding stubs.  We'll error out later should the
+            branch overflow.  */
+         if (tsec == isec)
            continue;
 
-         if (link_info->shared)
+         /* There probably isn't any reason to handle symbols in
+            SEC_MERGE sections;  SEC_MERGE doesn't seem a likely
+            attribute for a code section, and we are only looking at
+            branches.  However, implement it correctly here as a
+            reference for other target relax_section functions.  */
+         if (0 && tsec->sec_info_type == SEC_INFO_TYPE_MERGE)
            {
-             size = 4 * ARRAY_SIZE (shared_stub_entry);
-             insn_offset = 12;
+             /* At this stage in linking, no SEC_MERGE symbol has been
+                adjusted, so all references to such symbols need to be
+                passed through _bfd_merged_section_offset.  (Later, in
+                relocate_section, all SEC_MERGE symbols *except* for
+                section symbols have been adjusted.)
+
+                gas may reduce relocations against symbols in SEC_MERGE
+                sections to a relocation against the section symbol when
+                the original addend was zero.  When the reloc is against
+                a section symbol we should include the addend in the
+                offset passed to _bfd_merged_section_offset, since the
+                location of interest is the original symbol.  On the
+                other hand, an access to "sym+addend" where "sym" is not
+                a section symbol should not include the addend;  Such an
+                access is presumed to be an offset from "sym";  The
+                location of interest is just "sym".  */
+             if (sym_type == STT_SECTION)
+               toff += irel->r_addend;
+
+             toff
+               = _bfd_merged_section_offset (abfd, &tsec,
+                                             elf_section_data (tsec)->sec_info,
+                                             toff);
+
+             if (sym_type != STT_SECTION)
+               toff += irel->r_addend;
            }
-         else
+         /* PLTREL24 addends are special.  */
+         else if (r_type != R_PPC_PLTREL24)
+           toff += irel->r_addend;
+
+         /* Attempted -shared link of non-pic code loses.  */
+         if ((!link_info->relocatable
+              && tsec == bfd_und_section_ptr)
+             || tsec->output_section == NULL
+             || (tsec->owner != NULL
+                 && (tsec->owner->flags & BFD_PLUGIN) != 0))
+           continue;
+
+         roff = irel->r_offset;
+
+         /* If the branch is in range, no need to do anything.  */
+         if (tsec != bfd_und_section_ptr
+             && (!link_info->relocatable
+                 /* A relocatable link may have sections moved during
+                    final link, so do not presume they remain in range.  */
+                 || tsec->output_section == isec->output_section))
            {
-             size = 4 * ARRAY_SIZE (stub_entry);
-             insn_offset = 0;
+             bfd_vma symaddr, reladdr;
+
+             symaddr = tsec->output_section->vma + tsec->output_offset + toff;
+             reladdr = isec->output_section->vma + isec->output_offset + roff;
+             if (symaddr - reladdr + max_branch_offset
+                 < 2 * max_branch_offset)
+               continue;
            }
-         stub_rtype = R_PPC_RELAX;
-         if (tsec == htab->plt
-             || tsec == htab->glink)
+
+         /* Look for an existing fixup to this address.  */
+         for (f = fixups; f ; f = f->next)
+           if (f->tsec == tsec && f->toff == toff)
+             break;
+
+         if (f == NULL)
            {
-             stub_rtype = R_PPC_RELAX_PLT;
-             if (r_type == R_PPC_PLTREL24)
-               stub_rtype = R_PPC_RELAX_PLTREL24;
-           }
+             size_t size;
+             unsigned long stub_rtype;
 
-         /* Hijack the old relocation.  Since we need two
-            relocations for this use a "composite" reloc.  */
-         irel->r_info = ELF32_R_INFO (ELF32_R_SYM (irel->r_info),
-                                      stub_rtype);
-         irel->r_offset = trampoff + insn_offset;
-         if (r_type == R_PPC_PLTREL24
-             && stub_rtype != R_PPC_RELAX_PLTREL24)
-           irel->r_addend = 0;
-
-         /* Record the fixup so we don't do it again this section.  */
-         f = bfd_malloc (sizeof (*f));
-         f->next = fixups;
-         f->tsec = tsec;
-         f->toff = toff;
-         f->trampoff = trampoff;
-         fixups = f;
-
-         trampoff += size;
-         changes++;
-       }
-      else
-       {
-         val = f->trampoff - roff;
-         if (val >= max_branch_offset)
-           continue;
+             val = trampoff - roff;
+             if (val >= max_branch_offset)
+               /* Oh dear, we can't reach a trampoline.  Don't try to add
+                  one.  We'll report an error later.  */
+               continue;
 
-         /* Nop out the reloc, since we're finalizing things here.  */
-         irel->r_info = ELF32_R_INFO (0, R_PPC_NONE);
-       }
+             if (link_info->shared)
+               {
+                 size = 4 * ARRAY_SIZE (shared_stub_entry);
+                 insn_offset = 12;
+               }
+             else
+               {
+                 size = 4 * ARRAY_SIZE (stub_entry);
+                 insn_offset = 0;
+               }
+             stub_rtype = R_PPC_RELAX;
+             if (tsec == htab->plt
+                 || tsec == htab->glink)
+               {
+                 stub_rtype = R_PPC_RELAX_PLT;
+                 if (r_type == R_PPC_PLTREL24)
+                   stub_rtype = R_PPC_RELAX_PLTREL24;
+               }
 
-      /* Get the section contents.  */
-      if (contents == NULL)
-       {
-         /* Get cached copy if it exists.  */
-         if (elf_section_data (isec)->this_hdr.contents != NULL)
-           contents = elf_section_data (isec)->this_hdr.contents;
+             /* Hijack the old relocation.  Since we need two
+                relocations for this use a "composite" reloc.  */
+             irel->r_info = ELF32_R_INFO (ELF32_R_SYM (irel->r_info),
+                                          stub_rtype);
+             irel->r_offset = trampoff + insn_offset;
+             if (r_type == R_PPC_PLTREL24
+                 && stub_rtype != R_PPC_RELAX_PLTREL24)
+               irel->r_addend = 0;
+
+             /* Record the fixup so we don't do it again this section.  */
+             f = bfd_malloc (sizeof (*f));
+             f->next = fixups;
+             f->tsec = tsec;
+             f->toff = toff;
+             f->trampoff = trampoff;
+             fixups = f;
+
+             trampoff += size;
+             changes++;
+           }
          else
            {
+             val = f->trampoff - roff;
+             if (val >= max_branch_offset)
+               continue;
+
+             /* Nop out the reloc, since we're finalizing things here.  */
+             irel->r_info = ELF32_R_INFO (0, R_PPC_NONE);
+           }
+
+         /* Get the section contents.  */
+         if (contents == NULL)
+           {
+             /* Get cached copy if it exists.  */
+             if (elf_section_data (isec)->this_hdr.contents != NULL)
+               contents = elf_section_data (isec)->this_hdr.contents;
              /* Go get them off disk.  */
-             if (!bfd_malloc_and_get_section (abfd, isec, &contents))
+             else if (!bfd_malloc_and_get_section (abfd, isec, &contents))
                goto error_return;
            }
-       }
 
-      /* Fix up the existing branch to hit the trampoline.  */
-      hit_addr = contents + roff;
-      switch (r_type)
-       {
-       case R_PPC_REL24:
-       case R_PPC_LOCAL24PC:
-       case R_PPC_PLTREL24:
-         t0 = bfd_get_32 (abfd, hit_addr);
-         t0 &= ~0x3fffffc;
-         t0 |= val & 0x3fffffc;
-         bfd_put_32 (abfd, t0, hit_addr);
-         break;
+         /* Fix up the existing branch to hit the trampoline.  */
+         hit_addr = contents + roff;
+         switch (r_type)
+           {
+           case R_PPC_REL24:
+           case R_PPC_LOCAL24PC:
+           case R_PPC_PLTREL24:
+             t0 = bfd_get_32 (abfd, hit_addr);
+             t0 &= ~0x3fffffc;
+             t0 |= val & 0x3fffffc;
+             bfd_put_32 (abfd, t0, hit_addr);
+             break;
 
-       case R_PPC_REL14:
-       case R_PPC_REL14_BRTAKEN:
-       case R_PPC_REL14_BRNTAKEN:
-         t0 = bfd_get_32 (abfd, hit_addr);
-         t0 &= ~0xfffc;
-         t0 |= val & 0xfffc;
-         bfd_put_32 (abfd, t0, hit_addr);
-         break;
+           case R_PPC_REL14:
+           case R_PPC_REL14_BRTAKEN:
+           case R_PPC_REL14_BRNTAKEN:
+             t0 = bfd_get_32 (abfd, hit_addr);
+             t0 &= ~0xfffc;
+             t0 |= val & 0xfffc;
+             bfd_put_32 (abfd, t0, hit_addr);
+             break;
+           }
        }
-    }
 
-  /* Write out the trampolines.  */
-  if (fixups != NULL)
-    {
-      const int *stub;
-      bfd_byte *dest;
-      int i, size;
-
-      do
+      while (fixups != NULL)
        {
          struct one_fixup *f = fixups;
          fixups = fixups->next;
          free (f);
        }
-      while (fixups);
-
-      contents = bfd_realloc_or_free (contents, trampoff);
-      if (contents == NULL)
-       goto error_return;
-
-      isec->size = (isec->size + 3) & (bfd_vma) -4;
-      dest = contents + isec->size;
-      /* Branch around the trampolines.  */
-      if (maybe_pasted)
-       {
-         bfd_vma val = B + trampoff - isec->size;
-         bfd_put_32 (abfd, val, dest);
-         dest += 4;
-       }
-      isec->size = trampoff;
-
-      if (link_info->shared)
-       {
-         stub = shared_stub_entry;
-         size = ARRAY_SIZE (shared_stub_entry);
-       }
-      else
-       {
-         stub = stub_entry;
-         size = ARRAY_SIZE (stub_entry);
-       }
+    }
 
-      i = 0;
-      while (dest < contents + trampoff)
+  workaround_change = FALSE;
+  newsize = trampoff;
+  if (htab->params->ppc476_workaround
+      && (!link_info->relocatable
+         || isec->output_section->alignment_power >= htab->params->pagesize_p2))
+    {
+      bfd_vma addr, end_addr;
+      unsigned int crossings;
+      bfd_vma pagesize = (bfd_vma) 1 << htab->params->pagesize_p2;
+
+      addr = isec->output_section->vma + isec->output_offset;
+      end_addr = addr + trampoff - 1;
+      addr &= -pagesize;
+      crossings = ((end_addr & -pagesize) - addr) >> htab->params->pagesize_p2;
+      if (crossings != 0)
        {
-         bfd_put_32 (abfd, stub[i], dest);
-         i++;
-         if (i == size)
-           i = 0;
-         dest += 4;
+         /* Keep space aligned, to ensure the patch code itself does
+            not cross a page.  Don't decrease size calculated on a
+            previous pass as otherwise we might never settle on a layout.  */
+         newsize = 15 - (end_addr & 15);
+         newsize += crossings * 16;
+         if (relax_info->workaround_size < newsize)
+           {
+             relax_info->workaround_size = newsize;
+             workaround_change = TRUE;
+           }
+         /* Ensure relocate_section is called.  */
+         isec->flags |= SEC_RELOC;
        }
-      BFD_ASSERT (i == 0);
+      newsize = trampoff + relax_info->workaround_size;
     }
 
+  if (changes || workaround_change)
+    isec->size = newsize;
+
   if (isymbuf != NULL
       && symtab_hdr->contents != (unsigned char *) isymbuf)
     {
@@ -7090,34 +7198,20 @@ ppc_elf_relax_section (bfd *abfd,
       rel_hdr = _bfd_elf_single_rel_hdr (isec);
       rel_hdr->sh_size += changes * rel_hdr->sh_entsize;
     }
-  else if (elf_section_data (isec)->relocs != internal_relocs)
+  else if (internal_relocs != NULL
+          && elf_section_data (isec)->relocs != internal_relocs)
     free (internal_relocs);
 
-  *again = changes != 0;
-  if (!*again && link_info->relocatable)
-    {
-      /* Convert the internal relax relocs to external form.  */
-      for (irel = internal_relocs; irel < irelend; irel++)
-       if (ELF32_R_TYPE (irel->r_info) == R_PPC_RELAX)
-         {
-           unsigned long r_symndx = ELF32_R_SYM (irel->r_info);
-
-           /* Rewrite the reloc and convert one of the trailing nop
-              relocs to describe this relocation.  */
-           BFD_ASSERT (ELF32_R_TYPE (irelend[-1].r_info) == R_PPC_NONE);
-           /* The relocs are at the bottom 2 bytes */
-           irel[0].r_offset += 2;
-           memmove (irel + 1, irel, (irelend - irel - 1) * sizeof (*irel));
-           irel[0].r_info = ELF32_R_INFO (r_symndx, R_PPC_ADDR16_HA);
-           irel[1].r_offset += 4;
-           irel[1].r_info = ELF32_R_INFO (r_symndx, R_PPC_ADDR16_LO);
-           irel++;
-         }
-    }
-
+  *again = changes != 0 || workaround_change;
   return TRUE;
 
  error_return:
+  while (fixups != NULL)
+    {
+      struct one_fixup *f = fixups;
+      fixups = fixups->next;
+      free (f);
+    }
   if (isymbuf != NULL && (unsigned char *) isymbuf != symtab_hdr->contents)
     free (isymbuf);
   if (contents != NULL
@@ -7474,6 +7568,7 @@ ppc_elf_relocate_section (bfd *output_bfd,
       bfd_boolean warned;
       unsigned int tls_type, tls_mask, tls_gd;
       struct plt_entry **ifunc;
+      struct reloc_howto_struct alt_howto;
 
       r_type = ELF32_R_TYPE (rel->r_info);
       sym = NULL;
@@ -7493,10 +7588,12 @@ ppc_elf_relocate_section (bfd *output_bfd,
        }
       else
        {
+         bfd_boolean ignored;
+
          RELOC_FOR_GLOBAL_SYMBOL (info, input_bfd, input_section, rel,
                                   r_symndx, symtab_hdr, sym_hashes,
                                   h, sec, relocation,
-                                  unresolved_reloc, warned);
+                                  unresolved_reloc, warned, ignored);
 
          sym_name = h->root.root.string;
        }
@@ -7523,7 +7620,10 @@ ppc_elf_relocate_section (bfd *output_bfd,
                 addend specifies the GOT pointer offset within .got2.  */
              rel->r_addend += got2->output_offset;
            }
-         continue;
+         if (r_type != R_PPC_RELAX_PLT
+             && r_type != R_PPC_RELAX_PLTREL24
+             && r_type != R_PPC_RELAX)
+           continue;
        }
 
       /* TLS optimizations.  Replace instruction sequences and relocs
@@ -7861,7 +7961,8 @@ ppc_elf_relocate_section (bfd *output_bfd,
              unresolved_reloc = FALSE;
              if (htab->plt_type == PLT_NEW
                  || !htab->elf.dynamic_sections_created
-                 || h == NULL)
+                 || h == NULL
+                 || h->dynindx == -1)
                relocation = (htab->glink->output_section->vma
                              + htab->glink->output_offset
                              + (ent->glink_offset & ~1));
@@ -8150,9 +8251,9 @@ ppc_elf_relocate_section (bfd *output_bfd,
                 howto->name,
                 sym_name);
          }
-       break;
+         break;
 
-       /* Relocations that need no special processing.  */
+         /* Relocations that need no special processing.  */
        case R_PPC_LOCAL24PC:
          /* It makes no sense to point a local relocation
             at a symbol not in this object.  */
@@ -8437,36 +8538,59 @@ ppc_elf_relocate_section (bfd *output_bfd,
          /* Fall thru */
 
        case R_PPC_RELAX:
-         if (info->shared)
-           relocation -= (input_section->output_section->vma
-                          + input_section->output_offset
-                          + rel->r_offset - 4);
-
          {
-           unsigned long t0;
-           unsigned long t1;
-
-           t0 = bfd_get_32 (output_bfd, contents + rel->r_offset);
-           t1 = bfd_get_32 (output_bfd, contents + rel->r_offset + 4);
+           const int *stub;
+           size_t size;
+           size_t insn_offset = rel->r_offset;
+           unsigned int insn;
 
-           /* We're clearing the bits for R_PPC_ADDR16_HA
-              and R_PPC_ADDR16_LO here.  */
-           t0 &= ~0xffff;
-           t1 &= ~0xffff;
+           if (info->shared)
+             {
+               relocation -= (input_section->output_section->vma
+                              + input_section->output_offset
+                              + rel->r_offset - 4);
+               stub = shared_stub_entry;
+               bfd_put_32 (output_bfd, stub[0], contents + insn_offset - 12);
+               bfd_put_32 (output_bfd, stub[1], contents + insn_offset - 8);
+               bfd_put_32 (output_bfd, stub[2], contents + insn_offset - 4);
+               stub += 3;
+               size = ARRAY_SIZE (shared_stub_entry) - 3;
+             }
+           else
+             {
+               stub = stub_entry;
+               size = ARRAY_SIZE (stub_entry);
+             }
 
-           /* t0 is HA, t1 is LO */
            relocation += addend;
-           t0 |= ((relocation + 0x8000) >> 16) & 0xffff;
-           t1 |= relocation & 0xffff;
-
-           bfd_put_32 (output_bfd, t0, contents + rel->r_offset);
-           bfd_put_32 (output_bfd, t1, contents + rel->r_offset + 4);
+           if (info->relocatable)
+             relocation = 0;
+
+           /* First insn is HA, second is LO.  */
+           insn = *stub++;
+           insn |= ((relocation + 0x8000) >> 16) & 0xffff;
+           bfd_put_32 (output_bfd, insn, contents + insn_offset);
+           insn_offset += 4;
+
+           insn = *stub++;
+           insn |= relocation & 0xffff;
+           bfd_put_32 (output_bfd, insn, contents + insn_offset);
+           insn_offset += 4;
+           size -= 2;
+
+           while (size != 0)
+             {
+               insn = *stub++;
+               --size;
+               bfd_put_32 (output_bfd, insn, contents + insn_offset);
+               insn_offset += 4;
+             }
 
            /* Rewrite the reloc and convert one of the trailing nop
               relocs to describe this relocation.  */
            BFD_ASSERT (ELF32_R_TYPE (relend[-1].r_info) == R_PPC_NONE);
            /* The relocs are at the bottom 2 bytes */
-           rel[0].r_offset += 2;
+           rel[0].r_offset += d_offset;
            memmove (rel + 1, rel, (relend - rel - 1) * sizeof (*rel));
            rel[0].r_info = ELF32_R_INFO (r_symndx, R_PPC_ADDR16_HA);
            rel[1].r_offset += 4;
@@ -8618,45 +8742,39 @@ ppc_elf_relocate_section (bfd *output_bfd,
          break;
 
        case R_PPC_VLE_LO16A:
-         relocation = (relocation + addend) & 0xffff;
-         ppc_elf_vle_split16 (output_bfd, contents, rel->r_offset,
-                               relocation, split16a_type);
+         relocation = relocation + addend;
+         ppc_elf_vle_split16 (output_bfd, contents + rel->r_offset,
+                              relocation, split16a_type);
          continue;
 
        case R_PPC_VLE_LO16D:
-         relocation = (relocation + addend) & 0xffff;
-         ppc_elf_vle_split16 (output_bfd, contents, rel->r_offset,
-                               relocation, split16d_type);
+         relocation = relocation + addend;
+         ppc_elf_vle_split16 (output_bfd, contents + rel->r_offset,
+                              relocation, split16d_type);
          continue;
 
        case R_PPC_VLE_HI16A:
-         relocation = ((relocation + addend) >> 16) & 0xffff;
-         ppc_elf_vle_split16 (output_bfd, contents, rel->r_offset,
-                               relocation, split16a_type);
+         relocation = (relocation + addend) >> 16;
+         ppc_elf_vle_split16 (output_bfd, contents + rel->r_offset,
+                              relocation, split16a_type);
          continue;
 
        case R_PPC_VLE_HI16D:
-         relocation = ((relocation + addend) >> 16) & 0xffff;
-         ppc_elf_vle_split16 (output_bfd, contents, rel->r_offset,
-                               relocation, split16d_type);
+         relocation = (relocation + addend) >> 16;
+         ppc_elf_vle_split16 (output_bfd, contents + rel->r_offset,
+                              relocation, split16d_type);
          continue;
 
        case R_PPC_VLE_HA16A:
-         {
-           bfd_vma value = relocation + addend;
-           value = (((value >> 16) + ((value & 0x8000) ? 1 : 0)) & 0xffff);
-           ppc_elf_vle_split16 (output_bfd, contents, rel->r_offset,
-                                 value, split16a_type);
-         }
+         relocation = (relocation + addend + 0x8000) >> 16;
+         ppc_elf_vle_split16 (output_bfd, contents + rel->r_offset,
+                              relocation, split16a_type);
          continue;
 
        case R_PPC_VLE_HA16D:
-         {
-           bfd_vma value = relocation + addend;
-           value = (((value >> 16) + ((value & 0x8000) ? 1 : 0)) & 0xffff);
-           ppc_elf_vle_split16 (output_bfd, contents, rel->r_offset,
-                                 value, split16d_type);
-         }
+         relocation = (relocation + addend + 0x8000) >> 16;
+         ppc_elf_vle_split16 (output_bfd, contents + rel->r_offset,
+                              relocation, split16d_type);
          continue;
 
          /* Relocate against either _SDA_BASE_, _SDA2_BASE_, or 0.  */
@@ -8667,6 +8785,7 @@ ppc_elf_relocate_section (bfd *output_bfd,
          {
            const char *name;
            int reg;
+           unsigned int insn;
            struct elf_link_hash_entry *sda = NULL;
 
            if (sec == NULL || sec->output_section == NULL)
@@ -8720,32 +8839,40 @@ ppc_elf_relocate_section (bfd *output_bfd,
                addend -= SYM_VAL (sda);
              }
 
+           insn = bfd_get_32 (output_bfd, contents + rel->r_offset);
            if (reg == 0
                && (r_type == R_PPC_VLE_SDA21
                    || r_type == R_PPC_VLE_SDA21_LO))
              {
-               /* Use the split20 format.  */
-               bfd_vma insn, bits12to15, bits21to31;
-               bfd_vma value  = (relocation + rel->r_offset) & 0xffff;
-               /* Propagate sign bit, if necessary.  */
-               insn = (value & 0x8000) ? 0x70107800 : 0x70000000;
-               bits12to15 = value & 0x700;
-               bits21to31 = value & 0x7ff;
-               insn |= bits12to15;
-               insn |= bits21to31;
-               bfd_put_32 (output_bfd, insn, contents + rel->r_offset);
-               continue;
+               relocation = relocation + addend;
+               addend = 0;
+
+               /* Force e_li insn, keeping RT from original insn.  */
+               insn &= 0x1f << 21;
+               insn |= 28u << 26;
+
+               /* We have an li20 field, bits 17..20, 11..15, 21..31.  */
+               /* Top 4 bits of value to 17..20.  */
+               insn |= (relocation & 0xf0000) >> 5;
+               /* Next 5 bits of the value to 11..15.  */
+               insn |= (relocation & 0xf800) << 5;
+               /* And the final 11 bits of the value to bits 21 to 31.  */
+               insn |= relocation & 0x7ff;
+
+               /* Use _bfd_final_link_relocate to report overflow,
+                  but do so with a value that won't modify the insn.  */
+               if (relocation + 0x80000 > 0x100000)
+                 addend = 0x100000;
+               relocation = 0;
              }
            else if (r_type == R_PPC_EMB_SDA21
                     || r_type == R_PPC_VLE_SDA21
                     || r_type == R_PPC_VLE_SDA21_LO)
              {
-               bfd_vma insn;  /* Fill in register field.  */
-
-               insn = bfd_get_32 (output_bfd, contents + rel->r_offset);
+               /* Fill in register field.  */
                insn = (insn & ~RA_REGISTER_MASK) | (reg << RA_REGISTER_SHIFT);
-               bfd_put_32 (output_bfd, insn, contents + rel->r_offset);
              }
+           bfd_put_32 (output_bfd, insn, contents + rel->r_offset);
          }
          break;
 
@@ -8806,46 +8933,39 @@ ppc_elf_relocate_section (bfd *output_bfd,
                  }
              }
 
-          value = sda->root.u.def.section->output_section->vma
-                  + sda->root.u.def.section->output_offset;
-
-          if (r_type == R_PPC_VLE_SDAREL_LO16A)
-             {
-               value = (value + addend) & 0xffff;
-               ppc_elf_vle_split16 (output_bfd, contents, rel->r_offset,
-                                     value, split16a_type);
-             }
-          else if (r_type == R_PPC_VLE_SDAREL_LO16D)
-             {
-               value = (value + addend) & 0xffff;
-               ppc_elf_vle_split16 (output_bfd, contents, rel->r_offset,
-                                     value, split16d_type);
-             }
-          else if (r_type == R_PPC_VLE_SDAREL_HI16A)
+           value = (sda->root.u.def.section->output_section->vma
+                    + sda->root.u.def.section->output_offset
+                    + addend);
+
+           if (r_type == R_PPC_VLE_SDAREL_LO16A)
+             ppc_elf_vle_split16 (output_bfd, contents + rel->r_offset,
+                                  value, split16a_type);
+           else if (r_type == R_PPC_VLE_SDAREL_LO16D)
+             ppc_elf_vle_split16 (output_bfd, contents + rel->r_offset,
+                                  value, split16d_type);
+           else if (r_type == R_PPC_VLE_SDAREL_HI16A)
              {
-               value = ((value + addend) >> 16) & 0xffff;
-               ppc_elf_vle_split16 (output_bfd, contents, rel->r_offset,
-                                     value, split16a_type);
+               value = value >> 16;
+               ppc_elf_vle_split16 (output_bfd, contents + rel->r_offset,
+                                    value, split16a_type);
              }
-          else if (r_type == R_PPC_VLE_SDAREL_HI16D)
+           else if (r_type == R_PPC_VLE_SDAREL_HI16D)
              {
-               value = ((value + addend) >> 16) & 0xffff;
-               ppc_elf_vle_split16 (output_bfd, contents, rel->r_offset,
-                                     value, split16d_type);
+               value = value >> 16;
+               ppc_elf_vle_split16 (output_bfd, contents + rel->r_offset,
+                                    value, split16d_type);
              }
-          else if (r_type == R_PPC_VLE_SDAREL_HA16A)
+           else if (r_type == R_PPC_VLE_SDAREL_HA16A)
              {
-               value += addend;
-               value = (((value >> 16) + ((value & 0x8000) ? 1 : 0)) & 0xffff);
-               ppc_elf_vle_split16 (output_bfd, contents, rel->r_offset,
-                                     value, split16a_type);
+               value = (value + 0x8000) >> 16;
+               ppc_elf_vle_split16 (output_bfd, contents + rel->r_offset,
+                                    value, split16a_type);
              }
-          else if (r_type == R_PPC_VLE_SDAREL_HA16D)
+           else if (r_type == R_PPC_VLE_SDAREL_HA16D)
              {
-               value += addend;
-               value = (((value >> 16) + ((value & 0x8000) ? 1 : 0)) & 0xffff);
-               ppc_elf_vle_split16 (output_bfd, contents, rel->r_offset,
-                                     value, split16d_type);
+               value = (value + 0x8000) >> 16;
+               ppc_elf_vle_split16 (output_bfd, contents + rel->r_offset,
+                                    value, split16d_type);
              }
          }
          continue;
@@ -9005,13 +9125,36 @@ ppc_elf_relocate_section (bfd *output_bfd,
          ret = FALSE;
        }
 
-      r = _bfd_final_link_relocate (howto,
-                                   input_bfd,
-                                   input_section,
-                                   contents,
-                                   rel->r_offset,
-                                   relocation,
-                                   addend);
+      /* 16-bit fields in insns mostly have signed values, but a
+        few insns have 16-bit unsigned values.  Really, we should
+        have different reloc types.  */
+      if (howto->complain_on_overflow != complain_overflow_dont
+         && howto->dst_mask == 0xffff
+         && (input_section->flags & SEC_CODE) != 0)
+       {
+         enum complain_overflow complain = complain_overflow_signed;
+
+         if ((elf_section_flags (input_section) & SHF_PPC_VLE) == 0)
+           {
+             unsigned int insn;
+
+             insn = bfd_get_32 (input_bfd, contents + (rel->r_offset & ~3));
+             if ((insn & (0x3f << 26)) == 28u << 26 /* andi */
+                 || (insn & (0x3f << 26)) == 24u << 26 /* ori */
+                 || (insn & (0x3f << 26)) == 26u << 26 /* xori */
+                 || (insn & (0x3f << 26)) == 10u << 26 /* cmpli */)
+               complain = complain_overflow_unsigned;
+           }
+         if (howto->complain_on_overflow != complain)
+           {
+             alt_howto = *howto;
+             alt_howto.complain_on_overflow = complain;
+             howto = &alt_howto;
+           }
+       }
+
+      r = _bfd_final_link_relocate (howto, input_bfd, input_section, contents,
+                                   rel->r_offset, relocation, addend);
 
       if (r != bfd_reloc_ok)
        {
@@ -9057,6 +9200,206 @@ ppc_elf_relocate_section (bfd *output_bfd,
   fprintf (stderr, "\n");
 #endif
 
+  if (input_section->sec_info_type == SEC_INFO_TYPE_TARGET
+      && input_section->size != input_section->rawsize
+      && (strcmp (input_section->output_section->name, ".init") == 0
+         || strcmp (input_section->output_section->name, ".fini") == 0))
+    {
+      /* Branch around the trampolines.  */
+      unsigned int insn = B + input_section->size - input_section->rawsize;
+      bfd_put_32 (input_bfd, insn, contents + input_section->rawsize);
+    }
+
+  if (htab->params->ppc476_workaround
+      && input_section->sec_info_type == SEC_INFO_TYPE_TARGET
+      && (!info->relocatable
+         || (input_section->output_section->alignment_power
+             >= htab->params->pagesize_p2)))
+    {
+      struct ppc_elf_relax_info *relax_info;
+      bfd_vma start_addr, end_addr, addr;
+      bfd_vma pagesize = (bfd_vma) 1 << htab->params->pagesize_p2;
+
+      relax_info = elf_section_data (input_section)->sec_info;
+      if (relax_info->workaround_size != 0)
+       memset (contents + input_section->size - relax_info->workaround_size,
+               0, relax_info->workaround_size);
+
+      /* The idea is: Replace the last instruction on a page with a
+        branch to a patch area.  Put the insn there followed by a
+        branch back to the next page.  Complicated a little by
+        needing to handle moved conditional branches, and by not
+        wanting to touch data-in-text.  */
+
+      start_addr = (input_section->output_section->vma
+                   + input_section->output_offset);
+      end_addr = (start_addr + input_section->size
+                 - relax_info->workaround_size);
+      for (addr = ((start_addr & -pagesize) + pagesize - 4);
+          addr < end_addr;
+          addr += pagesize)
+       {
+         bfd_vma offset = addr - start_addr;
+         Elf_Internal_Rela *lo, *hi;
+         bfd_boolean is_data;
+         bfd_vma patch_off, patch_addr;
+         unsigned int insn;
+
+         /* Do we have a data reloc at this offset?  If so, leave
+            the word alone.  */
+         is_data = FALSE;
+         lo = relocs;
+         hi = relend;
+         rel = NULL;
+         while (lo < hi)
+           {
+             rel = lo + (hi - lo) / 2;
+             if (rel->r_offset < offset)
+               lo = rel + 1;
+             else if (rel->r_offset > offset + 3)
+               hi = rel;
+             else
+               {
+                 switch (ELF32_R_TYPE (rel->r_info))
+                   {
+                   case R_PPC_ADDR32:
+                   case R_PPC_UADDR32:
+                   case R_PPC_REL32:
+                   case R_PPC_ADDR30:
+                     is_data = TRUE;
+                     break;
+                   default:
+                     break;
+                   }
+                 break;
+               }
+           }
+         if (is_data)
+           continue;
+
+         /* Some instructions can be left alone too.  In this
+            category are most insns that unconditionally change
+            control flow, and isync.  Of these, some *must* be left
+            alone, for example, the "bcl 20, 31, label" used in pic
+            sequences to give the address of the next insn.  twui
+            and twu apparently are not safe.  */
+         insn = bfd_get_32 (input_bfd, contents + offset);
+         if (insn == 0
+             || (insn & (0x3f << 26)) == (18u << 26)        /* b */
+             || ((insn & (0x3f << 26)) == (16u << 26)       /* bc always */
+                 && (insn & (0x14 << 21)) == (0x14 << 21))
+             || ((insn & (0x3f << 26)) == (19u << 26)       /* blr, bctr */
+                 && (insn & (0x14 << 21)) == (0x14 << 21)
+                 && (insn & (0x1ff << 1)) == (16u << 1))
+             || (insn & (0x3f << 26)) == (17u << 26)        /* sc */
+             || ((insn & (0x3f << 26)) == (19u << 26)
+                 && ((insn & (0x3ff << 1)) == (38u << 1)    /* rfmci */
+                     || (insn & (0x3ff << 1)) == (50u << 1) /* rfi */
+                     || (insn & (0x3ff << 1)) == (51u << 1) /* rfci */
+                     || (insn & (0x3ff << 1)) == (82u << 1) /* rfsvc */
+                     || (insn & (0x3ff << 1)) == (150u << 1))) /* isync */)
+           continue;
+
+         patch_addr = (start_addr + input_section->size
+                       - relax_info->workaround_size);
+         patch_addr = (patch_addr + 15) & -16;
+         patch_off = patch_addr - start_addr;
+         bfd_put_32 (input_bfd, B + patch_off - offset, contents + offset);
+
+         if (rel != NULL
+             && rel->r_offset >= offset
+             && rel->r_offset < offset + 4)
+           {
+             /* If the insn we are patching had a reloc, adjust the
+                reloc r_offset so that the reloc applies to the moved
+                location.  This matters for -r and --emit-relocs.  */
+             if (rel + 1 != relend)
+               {
+                 Elf_Internal_Rela tmp = *rel;
+
+                 /* Keep the relocs sorted by r_offset.  */
+                 memmove (rel, rel + 1, (relend - (rel + 1)) * sizeof (*rel));
+                 relend[-1] = tmp;
+               }
+             relend[-1].r_offset += patch_off - offset;
+           }
+         else
+           rel = NULL;
+
+         if ((insn & (0x3f << 26)) == (16u << 26) /* bc */
+             && (insn & 2) == 0 /* relative */)
+           {
+             bfd_vma delta = ((insn & 0xfffc) ^ 0x8000) - 0x8000;
+
+             delta += offset - patch_off;
+             if (info->relocatable && rel != NULL)
+               delta = 0;
+             if (!info->relocatable && rel != NULL)
+               {
+                 enum elf_ppc_reloc_type r_type;
+
+                 r_type = ELF32_R_TYPE (relend[-1].r_info);
+                 if (r_type == R_PPC_REL14_BRTAKEN)
+                   insn |= BRANCH_PREDICT_BIT;
+                 else if (r_type == R_PPC_REL14_BRNTAKEN)
+                   insn &= ~BRANCH_PREDICT_BIT;
+                 else
+                   BFD_ASSERT (r_type == R_PPC_REL14);
+
+                 if ((r_type == R_PPC_REL14_BRTAKEN
+                      || r_type == R_PPC_REL14_BRNTAKEN)
+                     && delta + 0x8000 < 0x10000
+                     && (bfd_signed_vma) delta < 0)
+                   insn ^= BRANCH_PREDICT_BIT;
+               }
+             if (delta + 0x8000 < 0x10000)
+               {
+                 bfd_put_32 (input_bfd,
+                             (insn & ~0xfffc) | (delta & 0xfffc),
+                             contents + patch_off);
+                 patch_off += 4;
+                 bfd_put_32 (input_bfd,
+                             B | ((offset + 4 - patch_off) & 0x3fffffc),
+                             contents + patch_off);
+                 patch_off += 4;
+               }
+             else
+               {
+                 if (rel != NULL)
+                   {
+                     unsigned int r_sym = ELF32_R_SYM (relend[-1].r_info);
+
+                     relend[-1].r_offset += 8;
+                     relend[-1].r_info = ELF32_R_INFO (r_sym, R_PPC_REL24);
+                   }
+                 bfd_put_32 (input_bfd,
+                             (insn & ~0xfffc) | 8,
+                             contents + patch_off);
+                 patch_off += 4;
+                 bfd_put_32 (input_bfd,
+                             B | ((offset + 4 - patch_off) & 0x3fffffc),
+                             contents + patch_off);
+                 patch_off += 4;
+                 bfd_put_32 (input_bfd,
+                             B | ((delta - 8) & 0x3fffffc),
+                             contents + patch_off);
+                 patch_off += 4;
+               }
+           }
+         else
+           {
+             bfd_put_32 (input_bfd, insn, contents + patch_off);
+             patch_off += 4;
+             bfd_put_32 (input_bfd,
+                         B | ((offset + 4 - patch_off) & 0x3fffffc),
+                         contents + patch_off);
+             patch_off += 4;
+           }
+         BFD_ASSERT (patch_off <= input_section->size);
+         relax_info->workaround_size = input_section->size - patch_off;
+       }
+    }
+
   return ret;
 }
 \f
@@ -9331,7 +9674,7 @@ ppc_elf_finish_dynamic_symbol (bfd *output_bfd,
 
            p = (unsigned char *) htab->glink->contents + ent->glink_offset;
 
-           if (h == htab->tls_get_addr && !htab->no_tls_get_addr_opt)
+           if (h == htab->tls_get_addr && !htab->params->no_tls_get_addr_opt)
              {
                bfd_put_32 (output_bfd, LWZ_11_3, p);
                p += 4;
@@ -9882,6 +10225,7 @@ ppc_elf_finish_dynamic_sections (bfd *output_bfd,
 #define elf_backend_can_gc_sections    1
 #define elf_backend_can_refcount       1
 #define elf_backend_rela_normal                1
+#define elf_backend_caches_rawsize     1
 
 #define bfd_elf32_mkobject                     ppc_elf_mkobject
 #define bfd_elf32_bfd_merge_private_bfd_data   ppc_elf_merge_private_bfd_data
@@ -9920,7 +10264,6 @@ ppc_elf_finish_dynamic_sections (bfd *output_bfd,
 #define elf_backend_plt_sym_val                        ppc_elf_plt_sym_val
 #define elf_backend_action_discarded           ppc_elf_action_discarded
 #define elf_backend_init_index_section         _bfd_elf_init_1_index_section
-#define elf_backend_post_process_headers       _bfd_elf_set_osabi
 #define elf_backend_lookup_section_flags_hook  ppc_elf_lookup_section_flags
 #define elf_backend_section_processing         ppc_elf_section_processing