[PATCH] Support .cfi_personality and .cfi_lsda directives

Jakub Jelinek jakub@redhat.com
Wed Nov 1 16:56:00 GMT 2006


Hi!

This is the second part in my attempt to change GCC to emit .eh_frame unwind
info using .cfi_* directives.

The gas generated .eh_frame never has personality nor LSDA, while for C++,
Java or __attribute__((cleanup ())) in C we often need them.

This patch adds two new directives, .cfi_personality and .cfi_lsda,
which set the personality routine and LSDA of the current FDE (well,
personality routine is a CIE thingie, but we generate CIEs on the fly
for the FDEs).

Some compilers use a pointer in LSDA (e.g. GCC), others us a byte? (xlC),
and the encoding which should be used for them depends on whether we
are creating PIC or non-PIC code (which is information gas usually doesn't
know), so I think it is better to give assembler coders (and compilers)
freedom in choosing the encoding.
	.cfi_personality 0xff		# DW_EH_PE_omit
means no personality (the default after opening each .cfi_startproc),
while e.g.
	.cfi_personality 0x3, __gxx_personality_v0 # DW_EH_PE_udata4
puts DW_EH_PE_udata4 encoded __gxx_personality_v0 into the CIE.
As not all targets support symbol - . expressions, but some still can
do pcrel encodings, I think it is better if the second argument
is just the symbol itself, not the exact expression, so for pcrel encoded
LSDA something like this can be used:
	.cfi_lsda 0x1b, .LLSDA2		# DW_EH_PE_pcrel | DW_EH_PE_sdata4
But, for -fpic personality we need DW_EH_PE_pcrel | DW_EH_PE_indirect | DW_EH_PE_sdata4
encoding and in that case it is IMHO better to let the assembler writer
handle the sometimes desirable sharing of the indirect location rather
than making up magic symbol names and linkonce sections.  So, for
indirect encoded personality something like this can be used:
	.cfi_personality 0x9b, DW.ref.__gxx_personality_v0
...
        .hidden DW.ref.__gxx_personality_v0
        .weak   DW.ref.__gxx_personality_v0
        .section .gnu.linkonce.d.DW.ref.__gxx_personality_v0,"aw",@progbits
        .align 8
        .type   DW.ref.__gxx_personality_v0, @object
        .size   DW.ref.__gxx_personality_v0, 8
DW.ref.__gxx_personality_v0:
        .quad   __gxx_personality_v0

(the last chunk is what we were using already in GCC emitted code).

Ok?

My plans include still CFI directives which can save/restore CFI
instructions accross different FDEs (i.e. a generalized .cfi_remember_state
and .cfi_restore_state, handled in assembler rather than in the unwinder),
and teaching bfd/elf-eh-frame.c to be able to share all CIEs, not just the
last one.

2006-11-01  Jakub Jelinek  <jakub@redhat.com>

	* dw2gencfi.c (struct fde_entry): Add per_encoding, lsda_encoding,
	personality and lsda.
	(struct cie_entry): Add per_encoding, lsda_encoding and personality.
	(alloc_fde_entry): Initialize per_encoding and lsda_encoding.
	(cfi_pseudo_table): Handle .cfi_personality and .cfi_lsda.
	(dot_cfi_personality, dot_cfi_lsda, encoding_size): New functions.
	(output_cie): Output personality including its encoding and LSDA encoding.
	(output_fde): Output LSDA.
	(select_cie_for_fde): Don't share CIE if personality, its encoding or
	LSDA encoding are different.  Copy the 3 fields from fde_entry to
	cie_entry.
	* doc/as.texinfo (.cfi_personality, .cfi_lsda): Document.

--- gas/dw2gencfi.c.jj	2006-10-31 13:08:25.000000000 +0100
+++ gas/dw2gencfi.c	2006-11-01 16:52:10.000000000 +0100
@@ -88,6 +88,10 @@ struct fde_entry
   symbolS *end_address;
   struct cfi_insn_data *data;
   struct cfi_insn_data **last;
+  unsigned char per_encoding;
+  unsigned char lsda_encoding;
+  expressionS personality;
+  expressionS lsda;
   unsigned int return_column;
   unsigned int signal_frame;
 };
@@ -98,6 +102,9 @@ struct cie_entry
   symbolS *start_address;
   unsigned int return_column;
   unsigned int signal_frame;
+  unsigned char per_encoding;
+  unsigned char lsda_encoding;
+  expressionS personality;
   struct cfi_insn_data *first, *last;
 };
 
@@ -150,6 +157,8 @@ alloc_fde_entry (void)
 
   fde->last = &fde->data;
   fde->return_column = DWARF2_DEFAULT_RETURN_COLUMN;
+  fde->per_encoding = DW_EH_PE_omit;
+  fde->lsda_encoding = DW_EH_PE_omit;
 
   return fde;
 }
@@ -369,6 +378,8 @@ static void dot_cfi (int);
 static void dot_cfi_escape (int);
 static void dot_cfi_startproc (int);
 static void dot_cfi_endproc (int);
+static void dot_cfi_personality (int);
+static void dot_cfi_lsda (int);
 
 /* Fake CFI type; outside the byte range of any real CFI insn.  */
 #define CFI_adjust_cfa_offset	0x100
@@ -397,6 +408,8 @@ const pseudo_typeS cfi_pseudo_table[] =
     { "cfi_window_save", dot_cfi, DW_CFA_GNU_window_save },
     { "cfi_escape", dot_cfi_escape, 0 },
     { "cfi_signal_frame", dot_cfi, CFI_signal_frame },
+    { "cfi_personality", dot_cfi_personality, 0 },
+    { "cfi_lsda", dot_cfi_lsda, 0 },
     { NULL, NULL, 0 }
   };
 
@@ -623,6 +636,148 @@ dot_cfi_escape (int ignored ATTRIBUTE_UN
 }
 
 static void
+dot_cfi_personality (int ignored ATTRIBUTE_UNUSED)
+{
+  struct fde_entry *fde;
+  offsetT encoding;
+
+  if (frchain_now->frch_cfi_data == NULL)
+    {
+      as_bad (_("CFI instruction used without previous .cfi_startproc"));
+      ignore_rest_of_line ();
+      return;
+    }
+
+  fde = frchain_now->frch_cfi_data->cur_fde_data;
+  encoding = get_absolute_expression ();
+  if (encoding == DW_EH_PE_omit)
+    {
+      demand_empty_rest_of_line ();
+      fde->per_encoding = encoding;
+      return;
+    }
+
+  if ((encoding & 0xff) != encoding
+      || ((encoding & 0x70) != 0
+#if defined DIFF_EXPR_OK || defined tc_cfi_emit_pcrel_expr
+	  && (encoding & 0x70) != DW_EH_PE_pcrel
+#endif
+	  )
+	 /* leb128 can be handled, but does something actually need it?  */
+      || (encoding & 7) == DW_EH_PE_uleb128
+      || (encoding & 7) > DW_EH_PE_udata8)
+    {
+      as_bad (_("invalid or unsupported encoding in .cfi_personality"));
+      ignore_rest_of_line ();
+      return;
+    }
+
+  if (*input_line_pointer++ != ',')
+    {
+      as_bad (_(".cfi_personality requires encoding and symbol arguments"));
+      ignore_rest_of_line ();
+      return;
+    }
+
+  expression_and_evaluate (&fde->personality);
+  switch (fde->personality.X_op)
+    {
+    case O_symbol:
+      break;
+    case O_constant:
+      if ((encoding & 0x70) == DW_EH_PE_pcrel)
+	encoding = DW_EH_PE_omit;
+      break;
+    default:
+      encoding = DW_EH_PE_omit;
+      break;
+    }
+
+  fde->per_encoding = encoding;
+
+  if (encoding == DW_EH_PE_omit)
+    {
+      as_bad (_("wrong second argument to .cfi_personality"));
+      ignore_rest_of_line ();
+      return;
+    }
+
+  demand_empty_rest_of_line ();
+}
+
+static void
+dot_cfi_lsda (int ignored ATTRIBUTE_UNUSED)
+{
+  struct fde_entry *fde;
+  offsetT encoding;
+
+  if (frchain_now->frch_cfi_data == NULL)
+    {
+      as_bad (_("CFI instruction used without previous .cfi_startproc"));
+      ignore_rest_of_line ();
+      return;
+    }
+
+  fde = frchain_now->frch_cfi_data->cur_fde_data;
+  encoding = get_absolute_expression ();
+  if (encoding == DW_EH_PE_omit)
+    {
+      demand_empty_rest_of_line ();
+      fde->lsda_encoding = encoding;
+      return;
+    }
+
+  if ((encoding & 0xff) != encoding
+      || ((encoding & 0x70) != 0
+#if defined DIFF_EXPR_OK || defined tc_cfi_emit_pcrel_expr
+	  && (encoding & 0x70) != DW_EH_PE_pcrel
+#endif
+	  )
+	 /* leb128 can be handled, but does something actually need it?  */
+      || (encoding & 7) == DW_EH_PE_uleb128
+      || (encoding & 7) > DW_EH_PE_udata8)
+    {
+      as_bad (_("invalid or unsupported encoding in .cfi_lsda"));
+      ignore_rest_of_line ();
+      return;
+    }
+
+  if (*input_line_pointer++ != ',')
+    {
+      as_bad (_(".cfi_lsda requires encoding and symbol arguments"));
+      ignore_rest_of_line ();
+      return;
+    }
+
+  fde->lsda_encoding = encoding;
+
+  expression_and_evaluate (&fde->lsda);
+  switch (fde->lsda.X_op)
+    {
+    case O_symbol:
+      break;
+    case O_constant:
+      if ((encoding & 0x70) == DW_EH_PE_pcrel)
+	encoding = DW_EH_PE_omit;
+      break;
+    default:
+      encoding = DW_EH_PE_omit;
+      break;
+    }
+
+  fde->lsda_encoding = encoding;
+
+  if (encoding == DW_EH_PE_omit)
+    {
+      as_bad (_("wrong second argument to .cfi_lsda"));
+      ignore_rest_of_line ();
+      return;
+    }
+
+  demand_empty_rest_of_line ();
+}
+
+static void
 dot_cfi_startproc (int ignored ATTRIBUTE_UNUSED)
 {
   int simple = 0;
@@ -737,18 +892,18 @@ output_cfi_insn (struct cfi_insn_data *i
 	      out_one (DW_CFA_advance_loc + scaled);
 	    else if (delta <= 0xFF)
 	      {
-	        out_one (DW_CFA_advance_loc1);
-	        out_one (delta);
+		out_one (DW_CFA_advance_loc1);
+		out_one (delta);
 	      }
 	    else if (delta <= 0xFFFF)
 	      {
-	        out_one (DW_CFA_advance_loc2);
-	        out_two (delta);
+		out_one (DW_CFA_advance_loc2);
+		out_two (delta);
 	      }
 	    else
 	      {
-	        out_one (DW_CFA_advance_loc4);
-	        out_four (delta);
+		out_one (DW_CFA_advance_loc4);
+		out_four (delta);
 	      }
 	  }
 	else
@@ -873,12 +1028,33 @@ output_cfi_insn (struct cfi_insn_data *i
     }
 }
 
+static offsetT
+encoding_size (unsigned char encoding)
+{
+  if (encoding == DW_EH_PE_omit)
+    return 0;
+  switch (encoding & 0x7)
+    {
+    case 0:
+      return bfd_get_arch_size (stdoutput) == 64 ? 8 : 4;
+    case DW_EH_PE_udata2:
+      return 2;
+    case DW_EH_PE_udata4:
+      return 4;
+    case DW_EH_PE_udata8:
+      return 8;
+    default:
+      abort ();
+    }
+}
+
 static void
 output_cie (struct cie_entry *cie)
 {
   symbolS *after_size_address, *end_address;
   expressionS exp;
   struct cfi_insn_data *i;
+  offsetT augmentation_size;
 
   cie->start_address = symbol_temp_new_now ();
   after_size_address = symbol_temp_make ();
@@ -894,6 +1070,10 @@ output_cie (struct cie_entry *cie)
   out_four (0);					/* CIE id.  */
   out_one (DW_CIE_VERSION);			/* Version.  */
   out_one ('z');				/* Augmentation.  */
+  if (cie->per_encoding != DW_EH_PE_omit)
+    out_one ('P');
+  if (cie->lsda_encoding != DW_EH_PE_omit)
+    out_one ('L');
   out_one ('R');
   if (cie->signal_frame)
     out_one ('S');
@@ -904,7 +1084,32 @@ output_cie (struct cie_entry *cie)
     out_one (cie->return_column);
   else
     out_uleb128 (cie->return_column);
-  out_uleb128 (1);				/* Augmentation size.  */
+  augmentation_size = 1 + (cie->lsda_encoding != DW_EH_PE_omit);
+  if (cie->per_encoding != DW_EH_PE_omit)
+    augmentation_size += 1 + encoding_size (cie->per_encoding);
+  out_uleb128 (augmentation_size);		/* Augmentation size.  */
+  if (cie->per_encoding != DW_EH_PE_omit)
+    {
+      offsetT size = encoding_size (cie->per_encoding);
+      out_one (cie->per_encoding);
+      exp = cie->personality;
+      if ((cie->per_encoding & 0x70) == DW_EH_PE_pcrel)
+	{
+#ifdef DIFF_EXPR_OK
+	  exp.X_op = O_subtract;
+	  exp.X_op_symbol = symbol_temp_new_now ();
+	  emit_expr (&exp, size);
+#elif defined (tc_cfi_emit_pcrel_expr)
+	  tc_cfi_emit_pcrel_expr (&exp, size);
+#else
+	  abort ();
+#endif
+	}
+      else
+	emit_expr (&exp, size);
+    }
+  if (cie->lsda_encoding != DW_EH_PE_omit)
+    out_one (cie->lsda_encoding);
 #if defined DIFF_EXPR_OK || defined tc_cfi_emit_pcrel_expr
   out_one (DW_EH_PE_pcrel | DW_EH_PE_sdata4);
 #else
@@ -925,6 +1130,7 @@ output_fde (struct fde_entry *fde, struc
 {
   symbolS *after_size_address, *end_address;
   expressionS exp;
+  offsetT augmentation_size;
 
   after_size_address = symbol_temp_make ();
   end_address = symbol_temp_make ();
@@ -940,7 +1146,7 @@ output_fde (struct fde_entry *fde, struc
   exp.X_op_symbol = cie->start_address;
   emit_expr (&exp, 4);				/* CIE offset.  */
 
-#ifdef DIFF_EXPR_OK  
+#ifdef DIFF_EXPR_OK
   exp.X_add_symbol = fde->start_address;
   exp.X_op_symbol = symbol_temp_new_now ();
   emit_expr (&exp, 4);				/* Code offset.  */
@@ -960,7 +1166,27 @@ output_fde (struct fde_entry *fde, struc
   exp.X_op_symbol = fde->start_address;		/* Code length.  */
   emit_expr (&exp, 4);
 
-  out_uleb128 (0);				/* Augmentation size.  */
+  augmentation_size = encoding_size (fde->lsda_encoding);
+  out_uleb128 (augmentation_size);		/* Augmentation size.  */
+
+  if (fde->lsda_encoding != DW_EH_PE_omit)
+    {
+      exp = fde->lsda;
+      if ((fde->lsda_encoding & 0x70) == DW_EH_PE_pcrel)
+	{
+#ifdef DIFF_EXPR_OK
+	  exp.X_op = O_subtract;
+	  exp.X_op_symbol = symbol_temp_new_now ();
+	  emit_expr (&exp, augmentation_size);
+#elif defined (tc_cfi_emit_pcrel_expr)
+	  tc_cfi_emit_pcrel_expr (&exp, augmentation_size);
+#else
+	  abort ();
+#endif
+	}
+      else
+	emit_expr (&exp, augmentation_size);
+    }
 
   for (; first; first = first->next)
     output_cfi_insn (first);
@@ -978,8 +1204,31 @@ select_cie_for_fde (struct fde_entry *fd
   for (cie = cie_root; cie; cie = cie->next)
     {
       if (cie->return_column != fde->return_column
-	  || cie->signal_frame != fde->signal_frame)
+	  || cie->signal_frame != fde->signal_frame
+	  || cie->per_encoding != fde->per_encoding
+	  || cie->lsda_encoding != fde->lsda_encoding)
 	continue;
+      if (cie->per_encoding != DW_EH_PE_omit)
+	{
+	  if (cie->personality.X_op != fde->personality.X_op
+	      || cie->personality.X_add_number
+		 != fde->personality.X_add_number)
+	    continue;
+	  switch (cie->personality.X_op)
+	    {
+	    case O_constant:
+	      if (cie->personality.X_unsigned != fde->personality.X_unsigned)
+		continue;
+	      break;
+	    case O_symbol:
+	      if (cie->personality.X_add_symbol
+		  != fde->personality.X_add_symbol)
+		continue;
+	      break;
+	    default:
+	      abort ();
+	    }
+	}
       for (i = cie->first, j = fde->data;
 	   i != cie->last && j != NULL;
 	   i = i->next, j = j->next)
@@ -1052,6 +1301,9 @@ select_cie_for_fde (struct fde_entry *fd
   cie_root = cie;
   cie->return_column = fde->return_column;
   cie->signal_frame = fde->signal_frame;
+  cie->per_encoding = fde->per_encoding;
+  cie->lsda_encoding = fde->lsda_encoding;
+  cie->personality = fde->personality;
   cie->first = fde->data;
 
   for (i = cie->first; i ; i = i->next)
--- gas/doc/as.texinfo.jj	2006-09-25 18:26:03.000000000 +0200
+++ gas/doc/as.texinfo	2006-11-01 17:25:44.000000000 +0100
@@ -4102,6 +4102,25 @@ Don't forget to close the function by 
 unwind entry previously opened by
 @code{.cfi_startproc}, and emits it to @code{.eh_frame}.
 
+@section @code{.cfi_personality @var{encoding} [, @var{exp}]}
+@code{.cfi_personality} defines personality routine and its encoding.
+@var{encoding} must be a constant determining how the personality
+should be encoded.  If it is 255 (@code{DW_EH_PE_omit}), second
+argument is not present, otherwise second argument should be
+a constant or a symbol name.  When using indirect encodings,
+the symbol provided should be the location where personality
+can be loaded from, not the personality routine itself.
+The default after @code{.cfi_startproc} is @code{.cfi_personality 0xff},
+no personality routine.
+
+@section @code{.cfi_lsda @var{encoding} [, @var{exp}]}
+@code{.cfi_lsda} defines LSDA and its encoding.
+@var{encoding} must be a constant determining how the LSDA
+should be encoded.  If it is 255 (@code{DW_EH_PE_omit}), second
+argument is not present, otherwise second argument should be a constant
+or a symbol name.  The default after @code{.cfi_startproc} is @code{.cfi_lsda 0xff},
+no LSDA.
+
 @section @code{.cfi_def_cfa @var{register}, @var{offset}}
 @code{.cfi_def_cfa} defines a rule for computing CFA as: @i{take 
 address from @var{register} and add @var{offset} to it}.

	Jakub



More information about the Binutils mailing list