* tc-i386.c (TC_RELOC): New macro.
authorKen Raeburn <raeburn@cygnus>
Tue, 13 Sep 1994 02:15:23 +0000 (02:15 +0000)
committerKen Raeburn <raeburn@cygnus>
Tue, 13 Sep 1994 02:15:23 +0000 (02:15 +0000)
(struct _i386_insn): New field disp_reloc.
(GOT_symbol): New variable.
(operand_special_chars): Added square-brackets and at-sign.
(reloc) [BFD_ASSEMBLER]: Added new argument OTHER; if it is not NO_RELOC, just
return it.
(reloc) [! BFD_ASSEMBLER]: Add third argument to dummy macro.
(BFD_RELOC_386_PLT32, _GOT32, _GOTOFF) [! BFD_ASSEMBLER]: More dummy macros.
(tc_i386_fix_adjustable): New function.  Returns zero if symbol in fixup is not
local, to prevent relocations against externals from being dropped.
(md_assemble): Initialize disp_reloc field to NO_RELOC.  Pass disp_reloc field
to reloc() function, and use TC_RELOC to generate value to pass to fix_new_exp.
(md_assemble): Change 32-bit reloc against GOT_symbol into a GOTPC reloc.
(i386_operand): Initialize disp_reloc field to NO_RELOC.  Handle @GOTOFF, @PLT,
@GOT operands.  For GOTOFF relocations with local symbols, force generation of
the section symbol.
(md_estimate_size_before_relax): If GOT_symbol exists, decide we're generating
PIC code, and convert relocations against undefined symbols from PCREL to
PLT32.
(md_apply_fix_1) [OBJ_ELF]: Fix up values for dynamic-linking relocs.
(md_undefined_symbol): Notice GLOBAL_OFFSET_TABLE_NAME and set and return
GOT_symbol if it matches.
(F, MAP): Move macro definitions outside function.
(tc_gen_reloc): Only switch on size and pcrel if code wasn't already supplied
as PLT32. GOT32, GOTOFF, or GOTPC.  Convert BFD_RELOC_32 using GOT_symbol into
GOTPC.

* tc-i386.h (TC_RELOC, tc_fix_adjustable, TC_RELOC_GLOBAL_OFFSET_TABLE,
TC_RELOC_RTSYM_LOC_FIXUP): New macros.
(NEED_FX_R_TYPE): Define.
(LOCAL_LABEL): Accept ".X" prefix too.
(GLOBAL_OFFSET_TABLE_NAME): Default to "_GLOBAL_OFFSET_TABLE_".

gas/config/tc-i386.c

index e3caab803d3664223525e2d7b013f4614937fc5d..8ccb17fac1676414c16bf53720d47e178153b283 100644 (file)
 #include "obstack.h"
 #include "opcode/i386.h"
 
+#ifndef TC_RELOC
+#define TC_RELOC(X,Y) (Y)
+#endif
+
 /* 'md_assemble ()' gathers together information and puts it into a
    i386_insn. */
 
@@ -58,6 +62,13 @@ struct _i386_insn
     /* Displacements (if given) for each operand. */
     expressionS *disps[MAX_OPERANDS];
 
+    /* Relocation type for operand */
+#ifdef BFD_ASSEMBLER
+    enum bfd_reloc_code_real disp_reloc[MAX_OPERANDS];
+#else
+    int disp_reloc[MAX_OPERANDS];
+#endif
+
     /* Immediate operands (if given) for each operand. */
     expressionS *imms[MAX_OPERANDS];
 
@@ -137,7 +148,7 @@ static char digit_chars[256];
 #define is_digit_char(x) (digit_chars[(unsigned char) x])
 
 /* put here all non-digit non-letter charcters that may occur in an operand */
-static char operand_special_chars[] = "%$-+(,)*._~/<>|&^!:";
+static char operand_special_chars[] = "%$-+(,)*._~/<>|&^!:[@]";
 
 static char *ordinal_names[] = {"first", "second", "third"}; /* for printfs */
 
@@ -233,6 +244,8 @@ static reg_entry *parse_register PARAMS ((char *reg_string));
 static void s_bss PARAMS ((int));
 #endif
 
+symbolS *GOT_symbol;           /* Pre-defined "__GLOBAL_OFFSET_TABLE" */
+
 static INLINE unsigned long
 mode_from_disp_size (t)
      unsigned long t;
@@ -622,10 +635,13 @@ pt (t)
 \f
 #ifdef BFD_ASSEMBLER
 static bfd_reloc_code_real_type
-reloc (size, pcrel)
+reloc (size, pcrel, other)
      int size;
      int pcrel;
+     bfd_reloc_code_real_type other;
 {
+  if (other != NO_RELOC) return other;
+
   if (pcrel)
     switch (size)
       {
@@ -650,20 +666,38 @@ reloc (size, pcrel)
   return BFD_RELOC_NONE;
 }
 #else
-#define reloc(SIZE,PCREL)      0
+#define reloc(SIZE,PCREL,OTHER)        0
 #define BFD_RELOC_32           0
 #define BFD_RELOC_32_PCREL     0
+#define BFD_RELOC_386_PLT32    0
+#define BFD_RELOC_386_GOT32    0
+#define BFD_RELOC_386_GOTOFF   0
 #endif
 
+/*
+ * Here we decide which fixups can be adjusted to make them relative to
+ * the beginning of the section instead of the symbol.  Basically we need
+ * to make sure that the dynamic relocations are done correctly, so in
+ * some cases we force the original symbol to be used.
+ */
+tc_i386_fix_adjustable(fixP)
+     fixS * fixP;
+{
+       /* Prevent all adjustments to global symbols. */
+       if(!S_IS_LOCAL(fixP->fx_addsy)) return 0;
+       return 1;
+}
+
 /* This is the guts of the machine-dependent assembler.  LINE points to a
    machine dependent instruction.  This function is supposed to emit
    the frags/bytes it assembles to.  */
+
 void
 md_assemble (line)
      char *line;
 {
   /* Holds template once we've found it. */
-  register template *t;
+  template *t;
 
   /* Count the size of the instruction generated.  */
   int insn_size = 0;
@@ -671,8 +705,12 @@ md_assemble (line)
   /* Possible templates for current insn */
   templates *current_templates = (templates *) 0;
 
+  int j;
+
   /* Initialize globals. */
   memset (&i, '\0', sizeof (i));
+  for (j = 0; j < MAX_OPERANDS; j++)
+    i.disp_reloc[j] = NO_RELOC;
   memset (disp_expressions, '\0', sizeof (disp_expressions));
   memset (im_expressions, '\0', sizeof (im_expressions));
   save_stack_p = save_stack;   /* reset stack pointer */
@@ -1459,7 +1497,8 @@ md_assemble (line)
        else
          {
            fix_new_exp (frag_now, p - frag_now->fr_literal, size,
-                        i.disps[0], 1, reloc (size, 1));
+                        i.disps[0], 1, reloc (size, 1, i.disp_reloc[0]));
+
          }
       }
     else if (t->opcode_modifier & JumpInterSegment)
@@ -1587,7 +1626,8 @@ md_assemble (line)
                        p = frag_more (4);
                        insn_size += 4;
                        fix_new_exp (frag_now, p - frag_now->fr_literal, 4,
-                                    i.disps[n], 0, BFD_RELOC_32);
+                                           i.disps[n], 0, 
+                                           TC_RELOC(i.disp_reloc[n], BFD_RELOC_32));
                      }
                  }
              }
@@ -1631,19 +1671,34 @@ md_assemble (line)
                      }
                    else
                      {         /* not absolute_section */
-                       /* need a 32-bit fixup (don't support 8bit non-absolute ims) */
-                       /* try to support other sizes ... */
+                       /* Need a 32-bit fixup (don't support 8bit
+                          non-absolute ims).  Try to support other
+                          sizes ... */
+                       int r_type;
                        int size;
+                       int pcrel = 0;
+
                        if (i.types[n] & (Imm8 | Imm8S))
                          size = 1;
                        else if (i.types[n] & Imm16)
                          size = 2;
                        else
                          size = 4;
+                       r_type = reloc (size, 0, i.disp_reloc[0]);
                        p = frag_more (size);
                        insn_size += size;
+#ifdef BFD_ASSEMBLER
+                       if (r_type = BFD_RELOC_32
+                           && i.imms[n]->X_op == O_symbol
+                           && GOT_symbol
+                           && GOT_symbol == i.imms[n]->X_add_symbol)
+                         {
+                           r_type = BFD_RELOC_386_GOTPC;
+                           i.imms[n]->X_add_number += 3;
+                         }
+#endif
                        fix_new_exp (frag_now, p - frag_now->fr_literal, size,
-                                    i.imms[n], 0, reloc (size, 0));
+                                    i.imms[n], pcrel, r_type);
                      }
                  }
              }
@@ -1990,11 +2045,61 @@ i386_operand (operand_string)
          char *save_input_line_pointer;
          exp = &disp_expressions[i.disp_operands];
          i.disps[this_operand] = exp;
+         i.disp_reloc[this_operand] = NO_RELOC;
          i.disp_operands++;
          save_input_line_pointer = input_line_pointer;
          input_line_pointer = displacement_string_start;
          END_STRING_AND_SAVE (displacement_string_end);
+
+         {
+           /*
+            * We can have operands of the form
+            *   <symbol>@GOTOFF+<nnn>
+            * Take the easy way out here and copy everything
+            * into a temporary buffer...
+            */
+           register char *cp;
+           if (cp = strchr(input_line_pointer,'@')) {
+             char tmpbuf[BUFSIZ];
+             
+             if(!GOT_symbol)
+               GOT_symbol = symbol_find_or_make(GLOBAL_OFFSET_TABLE_NAME);
+
+             if (strncmp(cp+1, "PLT", 3) == 0) {
+               i.disp_reloc[this_operand] = BFD_RELOC_386_PLT32;
+               *cp = '\0';
+               strcpy(tmpbuf, input_line_pointer);
+               strcat(tmpbuf, cp+1+3);
+               *cp = '@';
+             } else if (strncmp(cp+1, "GOTOFF", 6) == 0) {
+               i.disp_reloc[this_operand] = BFD_RELOC_386_GOTOFF;
+               *cp = '\0';
+               strcpy(tmpbuf, input_line_pointer);
+               strcat(tmpbuf, cp+1+6);
+               *cp = '@';
+             } else if (strncmp(cp+1, "GOT", 3) == 0) {
+               i.disp_reloc[this_operand] = BFD_RELOC_386_GOT32;
+               *cp = '\0';
+               strcpy(tmpbuf, input_line_pointer);
+               strcat(tmpbuf, cp+1+3);
+               *cp = '@';
+             } else
+               as_bad("Bad reloc specifier '%s' in expression", cp+1);
+             input_line_pointer = tmpbuf;
+           }
+         }
+
          exp_seg = expression (exp);
+
+#ifdef BFD_ASSEMBLER
+         /* We do this to make sure that the section symbol is in
+            the symbol table.  We will ultimately change the relocation
+            to be relative to the beginning of the section */
+         if(i.disp_reloc[this_operand] == BFD_RELOC_386_GOTOFF &&
+            S_IS_LOCAL(exp->X_add_symbol))
+           section_symbol(exp->X_add_symbol->bsym->section);
+#endif
+
          if (*input_line_pointer)
            as_bad ("Ignoring junk '%s' after expression", input_line_pointer);
          RESTORE_END_STRING (displacement_string_end);
@@ -2103,8 +2208,17 @@ md_estimate_size_before_relax (fragP, segment)
          opcode[0] = 0xe9;     /* dword disp jmp */
          fragP->fr_fix += 4;
          fix_new (fragP, old_fr_fix, 4,
-                  fragP->fr_symbol,
-                  fragP->fr_offset, 1, BFD_RELOC_32_PCREL);
+                                fragP->fr_symbol,
+                  fragP->fr_offset, 1,
+                  (GOT_symbol && /* Not quite right - we should switch on
+                                    presence of @PLT, but I cannot see how
+                                    to get to that from here.  We should have
+                                    done this in md_assemble to really
+                                    get it right all of the time, but I
+                                    think it does not matter that much, as
+                                    this will be right most of the time. ERY*/
+                   S_GET_SEGMENT(fragP->fr_symbol) == undefined_section)?
+                  BFD_RELOC_386_PLT32 : BFD_RELOC_32_PCREL);
          break;
 
        default:
@@ -2115,7 +2229,12 @@ md_estimate_size_before_relax (fragP, segment)
          fragP->fr_fix += 1 + 4;       /* we've added an opcode byte */
          fix_new (fragP, old_fr_fix + 1, 4,
                   fragP->fr_symbol,
-                  fragP->fr_offset, 1, BFD_RELOC_32_PCREL);
+                  fragP->fr_offset, 1, 
+                  (GOT_symbol &&  /* Not quite right - we should switch on
+                                    presence of @PLT, but I cannot see how
+                                    to get to that from here.  ERY */
+                   S_GET_SEGMENT(fragP->fr_symbol) == undefined_section)?
+                  BFD_RELOC_386_PLT32 : BFD_RELOC_32_PCREL);
          break;
        }
       frag_wane (fragP);
@@ -2303,6 +2422,68 @@ md_apply_fix_1 (fixP, value)
        }
 #endif
     }
+
+  /* Fix a few things - the dynamic linker expects certain values here,
+     and we must not dissappoint it. */
+#ifdef OBJ_ELF
+  if(fixP->fx_addsy)
+    switch(fixP->fx_r_type) {
+    case BFD_RELOC_386_PLT32:
+      /* Make the jump instruction point to the address of the operand.  At
+        runtime we merely add the offset to the actual PLT entry. */
+      value = 0xfffffffc;
+      break;
+    case BFD_RELOC_386_GOTPC:
+/*
+ *  This is tough to explain.  We end up with this one if we have
+ * operands that look like "_GLOBAL_OFFSET_TABLE_+[.-.L284]".  The goal
+ * here is to obtain the absolute address of the GOT, and it is strongly
+ * preferable from a performance point of view to avoid using a runtime
+ * relocation for this.  The actual sequence of instructions often look 
+ * something like:
+ * 
+ *     call    .L66
+ * .L66:
+ *     popl    %ebx
+ *     addl    $_GLOBAL_OFFSET_TABLE_+[.-.L66],%ebx
+ * 
+ *     The call and pop essentially return the absolute address of
+ * the label .L66 and store it in %ebx.  The linker itself will
+ * ultimately change the first operand of the addl so that %ebx points to
+ * the GOT, but to keep things simple, the .o file must have this operand
+ * set so that it generates not the absolute address of .L66, but the
+ * absolute address of itself.  This allows the linker itself simply
+ * treat a GOTPC relocation as asking for a pcrel offset to the GOT to be
+ * added in, and the addend of the relocation is stored in the operand
+ * field for the instruction itself.
+ * 
+ *     Our job here is to fix the operand so that it would add the correct
+ * offset so that %ebx would point to itself.  The thing that is tricky is
+ * that .-.L66 will point to the beginning of the instruction, so we need
+ * to further modify the operand so that it will point to itself.
+ * There are other cases where you have something like:
+ * 
+ *     .long   $_GLOBAL_OFFSET_TABLE_+[.-.L66]
+ * 
+ * and here no correction would be required.  Internally in the assembler
+ * we treat operands of this form as not being pcrel since the '.' is 
+ * explicitly mentioned, and I wonder whether it would simplify matters
+ * to do it this way.  Who knows.  In earlier versions of the PIC patches,
+ * the pcrel_adjust field was used to store the correction, but since the
+ * expression is not pcrel, I felt it would be confusing to do it this way.
+ */
+      value += fixP->fx_where + fixP->fx_frag->fr_address - fixP->fx_offset;
+      break;
+    case BFD_RELOC_386_GOT32:
+      value = 0; /* Fully resolved at runtime.  No addend. */
+    case BFD_RELOC_386_GOTOFF:
+      /* Here everything should be correct already.  Just wanted to mention
+       this explicitly so no one things I forgot it. */
+    default:
+      break;
+    }
+#endif
+
 #endif
   md_number_to_chars (p, value, fixP->fx_size);
 }
@@ -2483,6 +2664,18 @@ symbolS *
 md_undefined_symbol (name)
      char *name;
 {
+       if (*name == '_' && *(name+1) == 'G'
+           && strcmp(name, GLOBAL_OFFSET_TABLE_NAME) == 0)
+         {
+           if(!GOT_symbol)
+             {
+               if(symbol_find(name)) 
+                 as_bad("GOT already in symbol table");
+               GOT_symbol = symbol_new (name, undefined_section, 
+                                        (valueT) 0, &zero_address_frag);
+             };
+           return GOT_symbol;
+         }
   return 0;
 }
 
@@ -2533,6 +2726,8 @@ s_bss (ignore)
 
 
 #ifdef BFD_ASSEMBLER
+#define F(SZ,PCREL)            (((SZ) << 1) + (PCREL))
+#define MAP(SZ,PCREL,TYPE)     case F(SZ,PCREL): code = (TYPE); break
 
 arelent *
 tc_gen_reloc (section, fixp)
@@ -2542,27 +2737,40 @@ tc_gen_reloc (section, fixp)
   arelent *rel;
   bfd_reloc_code_real_type code;
 
-#define F(SZ,PCREL)            (((SZ) << 1) + (PCREL))
-  switch (F (fixp->fx_size, fixp->fx_pcrel))
+  switch(fixp->fx_r_type)
     {
-#define MAP(SZ,PCREL,TYPE)     case F(SZ,PCREL): code = (TYPE); break
+    case BFD_RELOC_386_PLT32:
+    case BFD_RELOC_386_GOT32:
+    case BFD_RELOC_386_GOTOFF:
+    case BFD_RELOC_386_GOTPC:
+      code = fixp->fx_r_type;
+      break;
+    default:
+      switch (F (fixp->fx_size, fixp->fx_pcrel))
+       {
 #ifndef OBJ_ELF
-      MAP (1, 0, BFD_RELOC_8);
-      MAP (2, 0, BFD_RELOC_16);
+         MAP (1, 0, BFD_RELOC_8);
+         MAP (2, 0, BFD_RELOC_16);
 #endif
-      MAP (4, 0, BFD_RELOC_32);
+         MAP (4, 0, BFD_RELOC_32);
 #ifndef OBJ_ELF
-      MAP (1, 1, BFD_RELOC_8_PCREL);
-      MAP (2, 1, BFD_RELOC_16_PCREL);
+         MAP (1, 1, BFD_RELOC_8_PCREL);
+         MAP (2, 1, BFD_RELOC_16_PCREL);
 #endif
-      MAP (4, 1, BFD_RELOC_32_PCREL);
-    default:
-      as_bad ("Can not do %d byte %srelocation", fixp->fx_size,
-             fixp->fx_pcrel ? "pc-relative" : "");
+         MAP (4, 1, BFD_RELOC_32_PCREL);
+       default:
+         as_bad ("Can not do %d byte %srelocation", fixp->fx_size,
+                 fixp->fx_pcrel ? "pc-relative" : "");
+       }
     }
 #undef MAP
 #undef F
 
+  if (code == BFD_RELOC_32
+      && GOT_symbol
+      && fixp->fx_addsy == GOT_symbol)
+    code = BFD_RELOC_386_GOTPC;
+
   rel = (arelent *) bfd_alloc_by_size_t (stdoutput, sizeof (arelent));
   assert (rel != 0);
   rel->sym_ptr_ptr = &fixp->fx_addsy->bsym;