BaseTools/GenFw: Add X64 GOTPCREL Support to GenFw
authorZenith432 <zenith432@users.sourceforge.net>
Mon, 9 Jul 2018 12:58:15 +0000 (20:58 +0800)
committerLiming Gao <liming.gao@intel.com>
Wed, 11 Jul 2018 08:22:08 +0000 (16:22 +0800)
Adds support for the following X64 ELF relocations to GenFw
  R_X86_64_GOTPCREL
  R_X86_64_GOTPCRELX
  R_X86_64_REX_GOTPCRELX

Background:
The GCC49 and GCC5 toolchains use the small pie model for X64.  In the
small pie model, gcc emits a GOTPCREL relocation whenever C code takes
the address of a global function.  The emission of GOTPCREL is mitigated
by several factors
1. In GCC49, all global symbols are declared hidden thereby eliminating
the emission of GOTPCREL.
2. In GCC5, LTO is used.  In LTO, the complier first creates intermediate
representation (IR) files.  During the static link stage, the LTO compiler
combines all IR files as a single compilation unit, using linker symbol
assistance to generate code.  Any global symbols defined in the IR that
are not referenced from outside the IR are converted to local symbols -
thereby eliminating the emission of GOTPCREL for them.
3. The linker (binutils ld) further transforms any GOTPCREL used with
the movq opcode to a direct rip-relative relocation used with the leaq
opcode.  This linker optimization can be disabled with the option
-Wl,--no-relax.  Furthermore, gcc is able to emit GOTPCREL with other
opcodes
  - pushq opcode for passing arguments to functions.
  - addq/subq opcodes for pointer arithmetic.
These other opcode uses are not transformed by the linker.
Ultimately, in GCC5 there are some emissions of GOTPCREL that survive
all these mitigations - if C code takes the address of a global function
defined in assembly code - and performs pointer arithmetic on the
address - then the GOTPCREL remains in the final linker product.
A GOTPCREL relocation today causes the build to stop since GenFw does
not handle them.  It is possible to eliminate any remaining GOTPCREL
emissions by manually declaring the global symbols causing them to have
hidden visibility.  This patch is offered instead to allow GenFw to
handle any residual GOTPCREL.

Cc: Shi Steven <steven.shi@intel.com>
Cc: Yonghong Zhu <yonghong.zhu@intel.com>
Cc: Liming Gao <liming.gao@intel.com>
Contributed-under: TianoCore Contribution Agreement 1.1
Signed-off-by: Zenith432 <zenith432@users.sourceforge.net>
Reviewed-by: Liming Gao <liming.gao@intel.com>
BaseTools/Source/C/GenFw/Elf64Convert.c
BaseTools/Source/C/GenFw/elf_common.h

index 4636cfe..9035112 100644 (file)
@@ -94,6 +94,15 @@ STATIC Elf_Ehdr *mEhdr;
 STATIC Elf_Shdr *mShdrBase;\r
 STATIC Elf_Phdr *mPhdrBase;\r
 \r
+//\r
+// GOT information\r
+//\r
+STATIC Elf_Shdr *mGOTShdr = NULL;\r
+STATIC UINT32   mGOTShindex = 0;\r
+STATIC UINT32   *mGOTCoffEntries = NULL;\r
+STATIC UINT32   mGOTMaxCoffEntries = 0;\r
+STATIC UINT32   mGOTNumCoffEntries = 0;\r
+\r
 //\r
 // Coff information\r
 //\r
@@ -322,6 +331,134 @@ GetSymName (
   return StrtabContents + Sym->st_name;\r
 }\r
 \r
+//\r
+// Find the ELF section hosting the GOT from an ELF Rva\r
+//   of a single GOT entry.  Normally, GOT is placed in\r
+//   ELF .text section, so assume once we find in which\r
+//   section the GOT is, all GOT entries are there, and\r
+//   just verify this.\r
+//\r
+STATIC\r
+VOID\r
+FindElfGOTSectionFromGOTEntryElfRva (\r
+  Elf64_Addr GOTEntryElfRva\r
+  )\r
+{\r
+  UINT32 i;\r
+  if (mGOTShdr != NULL) {\r
+    if (GOTEntryElfRva >= mGOTShdr->sh_addr &&\r
+        GOTEntryElfRva <  mGOTShdr->sh_addr + mGOTShdr->sh_size) {\r
+      return;\r
+    }\r
+    Error (NULL, 0, 3000, "Unsupported", "FindElfGOTSectionFromGOTEntryElfRva: GOT entries found in multiple sections.");\r
+    exit(EXIT_FAILURE);\r
+  }\r
+  for (i = 0; i < mEhdr->e_shnum; i++) {\r
+    Elf_Shdr *shdr = GetShdrByIndex(i);\r
+    if (GOTEntryElfRva >= shdr->sh_addr &&\r
+        GOTEntryElfRva <  shdr->sh_addr + shdr->sh_size) {\r
+      mGOTShdr = shdr;\r
+      mGOTShindex = i;\r
+      return;\r
+    }\r
+  }\r
+  Error (NULL, 0, 3000, "Invalid", "FindElfGOTSectionFromGOTEntryElfRva: ElfRva 0x%016LX for GOT entry not found in any section.", GOTEntryElfRva);\r
+  exit(EXIT_FAILURE);\r
+}\r
+\r
+//\r
+// Stores locations of GOT entries in COFF image.\r
+//   Returns TRUE if GOT entry is new.\r
+//   Simple implementation as number of GOT\r
+//   entries is expected to be low.\r
+//\r
+\r
+STATIC\r
+BOOLEAN\r
+AccumulateCoffGOTEntries (\r
+  UINT32 GOTCoffEntry\r
+  )\r
+{\r
+  UINT32 i;\r
+  if (mGOTCoffEntries != NULL) {\r
+    for (i = 0; i < mGOTNumCoffEntries; i++) {\r
+      if (mGOTCoffEntries[i] == GOTCoffEntry) {\r
+        return FALSE;\r
+      }\r
+    }\r
+  }\r
+  if (mGOTCoffEntries == NULL) {\r
+    mGOTCoffEntries = (UINT32*)malloc(5 * sizeof *mGOTCoffEntries);\r
+    if (mGOTCoffEntries == NULL) {\r
+      Error (NULL, 0, 4001, "Resource", "memory cannot be allocated!");\r
+    }\r
+    assert (mGOTCoffEntries != NULL);\r
+    mGOTMaxCoffEntries = 5;\r
+    mGOTNumCoffEntries = 0;\r
+  } else if (mGOTNumCoffEntries == mGOTMaxCoffEntries) {\r
+    mGOTCoffEntries = (UINT32*)realloc(mGOTCoffEntries, 2 * mGOTMaxCoffEntries * sizeof *mGOTCoffEntries);\r
+    if (mGOTCoffEntries == NULL) {\r
+      Error (NULL, 0, 4001, "Resource", "memory cannot be allocated!");\r
+    }\r
+    assert (mGOTCoffEntries != NULL);\r
+    mGOTMaxCoffEntries += mGOTMaxCoffEntries;\r
+  }\r
+  mGOTCoffEntries[mGOTNumCoffEntries++] = GOTCoffEntry;\r
+  return TRUE;\r
+}\r
+\r
+//\r
+// 32-bit Unsigned integer comparator for qsort.\r
+//\r
+STATIC\r
+int\r
+UINT32Comparator (\r
+  const void* lhs,\r
+  const void* rhs\r
+  )\r
+{\r
+  if (*(const UINT32*)lhs < *(const UINT32*)rhs) {\r
+    return -1;\r
+  }\r
+  return *(const UINT32*)lhs > *(const UINT32*)rhs;\r
+}\r
+\r
+//\r
+// Emit accumulated Coff GOT entry relocations into\r
+//   Coff image.  This function performs its job\r
+//   once and then releases the entry list, so\r
+//   it can safely be called multiple times.\r
+//\r
+STATIC\r
+VOID\r
+EmitGOTRelocations (\r
+  VOID\r
+  )\r
+{\r
+  UINT32 i;\r
+  if (mGOTCoffEntries == NULL) {\r
+    return;\r
+  }\r
+  //\r
+  // Emit Coff relocations with Rvas ordered.\r
+  //\r
+  qsort(\r
+    mGOTCoffEntries,\r
+    mGOTNumCoffEntries,\r
+    sizeof *mGOTCoffEntries,\r
+    UINT32Comparator);\r
+  for (i = 0; i < mGOTNumCoffEntries; i++) {\r
+    VerboseMsg ("EFI_IMAGE_REL_BASED_DIR64 Offset: 0x%08X", mGOTCoffEntries[i]);\r
+    CoffAddFixup(\r
+      mGOTCoffEntries[i],\r
+      EFI_IMAGE_REL_BASED_DIR64);\r
+  }\r
+  free(mGOTCoffEntries);\r
+  mGOTCoffEntries = NULL;\r
+  mGOTMaxCoffEntries = 0;\r
+  mGOTNumCoffEntries = 0;\r
+}\r
+\r
 //\r
 // Elf functions interface implementation\r
 //\r
@@ -643,6 +780,7 @@ WriteSections64 (
   Elf_Shdr    *SecShdr;\r
   UINT32      SecOffset;\r
   BOOLEAN     (*Filter)(Elf_Shdr *);\r
+  Elf64_Addr  GOTEntryRva;\r
 \r
   //\r
   // Initialize filter pointer\r
@@ -710,7 +848,7 @@ WriteSections64 (
     // section that applies to the entire binary, and which will have its section\r
     // index set to #0 (which is a NULL section with the SHF_ALLOC bit cleared).\r
     //\r
-    // In the absence of GOT based relocations (which we currently don't support),\r
+    // In the absence of GOT based relocations,\r
     // this RELA section will contain redundant R_xxx_RELATIVE relocations, one\r
     // for every R_xxx_xx64 relocation appearing in the per-section RELA sections.\r
     // (i.e., .rela.text and .rela.data)\r
@@ -846,6 +984,44 @@ WriteSections64 (
               - (SecOffset - SecShdr->sh_addr));\r
             VerboseMsg ("Relocation:  0x%08X", *(UINT32 *)Targ);\r
             break;\r
+          case R_X86_64_GOTPCREL:\r
+          case R_X86_64_GOTPCRELX:\r
+          case R_X86_64_REX_GOTPCRELX:\r
+            VerboseMsg ("R_X86_64_GOTPCREL family");\r
+            VerboseMsg ("Offset: 0x%08X, Addend: 0x%08X",\r
+              (UINT32)(SecOffset + (Rel->r_offset - SecShdr->sh_addr)),\r
+              *(UINT32 *)Targ);\r
+            GOTEntryRva = Rel->r_offset - Rel->r_addend + *(INT32 *)Targ;\r
+            FindElfGOTSectionFromGOTEntryElfRva(GOTEntryRva);\r
+            *(UINT32 *)Targ = (UINT32) (*(UINT32 *)Targ\r
+              + (mCoffSectionsOffset[mGOTShindex] - mGOTShdr->sh_addr)\r
+              - (SecOffset - SecShdr->sh_addr));\r
+            VerboseMsg ("Relocation:  0x%08X", *(UINT32 *)Targ);\r
+            GOTEntryRva += (mCoffSectionsOffset[mGOTShindex] - mGOTShdr->sh_addr);  // ELF Rva -> COFF Rva\r
+            if (AccumulateCoffGOTEntries((UINT32)GOTEntryRva)) {\r
+              //\r
+              // Relocate GOT entry if it's the first time we run into it\r
+              //\r
+              Targ = mCoffFile + GOTEntryRva;\r
+              //\r
+              // Limitation: The following three statements assume memory\r
+              //   at *Targ is valid because the section containing the GOT\r
+              //   has already been copied from the ELF image to the Coff image.\r
+              //   This pre-condition presently holds because the GOT is placed\r
+              //   in section .text, and the ELF text sections are all copied\r
+              //   prior to reaching this point.\r
+              //   If the pre-condition is violated in the future, this fixup\r
+              //   either needs to be deferred after the GOT section is copied\r
+              //   to the Coff image, or the fixup should be performed on the\r
+              //   source Elf image instead of the destination Coff image.\r
+              //\r
+              VerboseMsg ("Offset: 0x%08X, Addend: 0x%016LX",\r
+                (UINT32)GOTEntryRva,\r
+                *(UINT64 *)Targ);\r
+              *(UINT64 *)Targ = *(UINT64 *)Targ - SymShdr->sh_addr + mCoffSectionsOffset[Sym->st_shndx];\r
+              VerboseMsg ("Relocation:  0x%016LX", *(UINT64*)Targ);\r
+            }\r
+            break;\r
           default:\r
             Error (NULL, 0, 3000, "Invalid", "%s unsupported ELF EM_X86_64 relocation 0x%x.", mInImageName, (unsigned) ELF_R_TYPE(Rel->r_info));\r
           }\r
@@ -984,6 +1160,9 @@ WriteRelocations64 (
             case R_X86_64_NONE:\r
             case R_X86_64_PC32:\r
             case R_X86_64_PLT32:\r
+            case R_X86_64_GOTPCREL:\r
+            case R_X86_64_GOTPCRELX:\r
+            case R_X86_64_REX_GOTPCRELX:\r
               break;\r
             case R_X86_64_64:\r
               VerboseMsg ("EFI_IMAGE_REL_BASED_DIR64 Offset: 0x%08X",\r
@@ -1052,10 +1231,32 @@ WriteRelocations64 (
             Error (NULL, 0, 3000, "Not Supported", "This tool does not support relocations for ELF with e_machine %u (processor type).", (unsigned) mEhdr->e_machine);\r
           }\r
         }\r
+        if (mEhdr->e_machine == EM_X86_64 && RelShdr->sh_info == mGOTShindex) {\r
+          //\r
+          // Tack relocations for GOT entries after other relocations for\r
+          //   the section the GOT is in, as it's usually found at the end\r
+          //   of the section.  This is done in order to maintain Rva order\r
+          //   of Coff relocations.\r
+          //\r
+          EmitGOTRelocations();\r
+        }\r
       }\r
     }\r
   }\r
 \r
+  if (mEhdr->e_machine == EM_X86_64) {\r
+    //\r
+    // This is a safety net just in case the GOT is in a section\r
+    //   with no other relocations and the first invocation of\r
+    //   EmitGOTRelocations() above was skipped.  This invocation\r
+    //   does not maintain Rva order of Coff relocations.\r
+    //   At present, with a single text section, all references to\r
+    //   the GOT and the GOT itself reside in section .text, so\r
+    //   if there's a GOT at all, the first invocation above\r
+    //   is executed.\r
+    //\r
+    EmitGOTRelocations();\r
+  }\r
   //\r
   // Pad by adding empty entries.\r
   //\r
index 242ad00..03dec50 100644 (file)
@@ -1052,6 +1052,23 @@ typedef struct {
 #define  R_X86_64_DTPOFF32  21  /* Offset in TLS block */\r
 #define  R_X86_64_GOTTPOFF  22  /* PC relative offset to IE GOT entry */\r
 #define  R_X86_64_TPOFF32  23  /* Offset in static TLS block */\r
+#define  R_X86_64_PC64  24  /* PC relative 64 bit */\r
+#define  R_X86_64_GOTOFF64  25  /* 64 bit offset to GOT */\r
+#define  R_X86_64_GOTPC3  26  /* 32 bit signed pc relative offset to GOT */\r
+#define  R_X86_64_GOT64  27  /* 64-bit GOT entry offset */\r
+#define  R_X86_64_GOTPCREL64  28  /* 64-bit PC relative offset to GOT entry */\r
+#define  R_X86_64_GOTPC64  29  /* 64-bit PC relative offset to GOT */\r
+#define  R_X86_64_GOTPLT64  30  /* like GOT64, says PLT entry needed */\r
+#define  R_X86_64_PLTOFF64  31  /* 64-bit GOT relative offset to PLT entry */\r
+#define  R_X86_64_SIZE32  32  /* Size of symbol plus 32-bit addend */\r
+#define  R_X86_64_SIZE64  33  /* Size of symbol plus 64-bit addend */\r
+#define  R_X86_64_GOTPC32_TLSDESC  34  /* GOT offset for TLS descriptor. */\r
+#define  R_X86_64_TLSDESC_CALL  35  /* Marker for call through TLS descriptor. */\r
+#define  R_X86_64_TLSDESC  36  /* TLS descriptor. */\r
+#define  R_X86_64_IRELATIVE  37  /* Adjust indirectly by program base */\r
+#define  R_X86_64_RELATIVE64  38  /* 64-bit adjust by program base */\r
+#define  R_X86_64_GOTPCRELX  41  /* Load from 32 bit signed pc relative offset to GOT entry without REX prefix, relaxable. */\r
+#define  R_X86_64_REX_GOTPCRELX  42  /* Load from 32 bit signed pc relative offset to GOT entry with REX prefix, relaxable. */\r
 \r
 \r
 #endif /* !_SYS_ELF_COMMON_H_ */\r