[v1,1/1] Enable arch-specific CFI directives and DWARF instructions only when required by the target (part 1)

Message ID 20250409103611.407140-2-matthieu.longo@arm.com
State New
Headers
Series Enable arch-specific CFI directives and DWARF instructions only when required by the target (part 1) |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_binutils_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_binutils_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_binutils_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_binutils_check--master-aarch64 success Test passed

Commit Message

Matthieu Longo April 9, 2025, 10:36 a.m. UTC
  DWARF 5 standard provides a space for vendor-specific DWARF directives.
Binutils currently defines GNU-specific DWARF directives and vendor-specific
ones for AArch64, SGI/MIPS, and Sparc architectures.

The architecture specific code is not guarded. This has caused confusion in
the past, and .cfi_window_save (a Sparc-specific directive) was used on
AArch64 in place of .cfi_negate_ra_state, the defined directive by the "DWARF
extensions on AArch64" document (see [1]).

Gas only supports one target architecture (the one selected with --target),
whereas others tools like readelf, objdump, etc. deduce the target
architecture from the input object at runtime. Hence, using TC_<arch>
definitions to enable/disable DWARF directives at compilation time outside
of gas is excluded.

Since Gas is target-specific, the solution consists in detecting whether
dwarf2.def is included from Gas or somewhere else. To do so, a new define
MODULE_GAS was added in configure.ac to allow to detect the inclusion
provenance, and the two following behaviors can be observed in dwarf2.def:
- if MODULE_GAS is defined (included from gas folder), the architecture-
  specific DWARF instructions are enabled/disabled via TC_<arch> defines.
- if MODULE_GAS is undefined (included from bfd and binutils folders), all
  the DWARF instructions are defined (as it is today).

In bfd, the architecture of the input object is detected at runtime using
bfd_get_arch, and only the architecture-specific instructions matching the
target of the object are considered during the processing.

This patch deliberately excluded from the scope binutils/dwarf.c that also
requires a check of the architecture at runtime. This part will be addressed
in a follow-up patch.

[1]: https://github.com/ARM-software/abi-aa/blob/main/aadwarf64/aadwarf64.rst#id2
---
 bfd/elf-eh-frame.c | 90 +++++++++++++++++++++++++++++++++++++++-------
 gas/config.in      |  3 ++
 gas/configure      |  4 +++
 gas/configure.ac   |  2 ++
 gas/dw2gencfi.c    | 33 ++++++++++++++---
 gas/gen-sframe.c   | 35 ++++--------------
 include/dwarf2.def | 38 ++++++++++++++++----
 7 files changed, 154 insertions(+), 51 deletions(-)
  

Comments

Jan Beulich April 10, 2025, 7:34 a.m. UTC | #1
On 09.04.2025 12:36, Matthieu Longo wrote:
> --- a/bfd/elf-eh-frame.c
> +++ b/bfd/elf-eh-frame.c
> @@ -338,12 +338,79 @@ next_cie_fde_offset (const struct eh_cie_fde *ent,
>    return sec->size;
>  }
>  
> +static bool
> +skip_cfa_op_aarch64 (bfd_byte op,
> +		     bfd_byte **iter ATTRIBUTE_UNUSED,
> +		     bfd_byte *end ATTRIBUTE_UNUSED)
> +{
> +  switch (op)
> +    {
> +      case DW_CFA_AARCH64_negate_ra_state:
> +      case DW_CFA_AARCH64_negate_ra_state_with_pc:

Much like it is in existing code you modify (further down), case labels want
to align with the opening brace. I.e. here and below you will want to un-
indent what's inside the figure braces of switch() by one level.

> +static bool
> +skip_cfa_op_arch_specific (enum bfd_architecture arch,
> +			   bfd_byte **iter, bfd_byte *end,
> +			   bfd_byte op)
> +{
> +  switch (arch)
> +    {
> +      case bfd_arch_aarch64:
> +	return skip_cfa_op_aarch64 (op, iter, end);
> +      case bfd_arch_mips:
> +	return skip_cfa_op_mips (op, iter, end);
> +      case bfd_arch_sparc:
> +	return skip_cfa_op_sparc (op, iter, end);

Hmm, I'm uncertain here - from the name DW_CFA_GNU_window_save isn't Sparc-
specific. Yet then I can see that gcc uses it only there.

In any event, much like you do further up, having blank lines between non-
fall-through case blocks would be nice.

> --- a/gas/configure.ac
> +++ b/gas/configure.ac
> @@ -937,6 +937,8 @@ AC_DEFINE_UNQUOTED(TARGET_CPU,		"${target_cpu}",   [Target CPU.])
>  AC_DEFINE_UNQUOTED(TARGET_VENDOR,	"${target_vendor}", [Target vendor.])
>  AC_DEFINE_UNQUOTED(TARGET_OS,		"${target_os}",    [Target OS.])
>  
> +AC_DEFINE(MODULE_GAS, 1, [Building gas module])

Hmm, here I'm again not sure: As this is unconditional, I'd have expected
the definition to come from Makefile.am instead. Question is whether we need
the extra #define in the first place: TC_* shouldn't be defined in subdirs
other than gas/. Then again dwarf2.def is shared with at least gcc, so
perhaps better to be on the safe side. However, as.h already defines GAS -
can't we key to that?

> --- a/gas/dw2gencfi.c
> +++ b/gas/dw2gencfi.c
> @@ -720,9 +720,16 @@ const pseudo_typeS cfi_pseudo_table[] =
>      { "cfi_same_value", dot_cfi, DW_CFA_same_value },
>      { "cfi_remember_state", dot_cfi, DW_CFA_remember_state },
>      { "cfi_restore_state", dot_cfi, DW_CFA_restore_state },
> -    { "cfi_window_save", dot_cfi, DW_CFA_GNU_window_save },
> +#if TC_AARCH64
> +    /* cfi_window_save is an alias of cfi_negate_ra_state which is kept for
> +       backward-compatibility concerns.  */
> +    { "cfi_window_save", dot_cfi, DW_CFA_AARCH64_negate_ra_state },
>      { "cfi_negate_ra_state", dot_cfi, DW_CFA_AARCH64_negate_ra_state },
>      { "cfi_negate_ra_state_with_pc", dot_cfi, DW_CFA_AARCH64_negate_ra_state_with_pc },
> +#endif /* TC_AARCH64 */
> +#if TC_SPARC
> +    { "cfi_window_save", dot_cfi, DW_CFA_GNU_window_save },
> +#endif /* TC_SPARC */

See comment further up (applicable also further down).

> --- a/include/dwarf2.def
> +++ b/include/dwarf2.def
> @@ -780,20 +780,46 @@ DW_CFA (DW_CFA_val_offset, 0x14)
>  DW_CFA (DW_CFA_val_offset_sf, 0x15)
>  DW_CFA (DW_CFA_val_expression, 0x16)
>  
> +/* Users extensions.
> +   Note: DW_CFA_lo_user and DW_CFA_hi_user can be aliased to used as extension.
> +   This is a closed interval.  */
>  DW_CFA (DW_CFA_lo_user, 0x1c)
> -DW_CFA (DW_CFA_hi_user, 0x3f)
>  
> -/* SGI/MIPS specific.  */
> +/* For gas, we disable the extensions according to the target architecture.  */
> +#ifdef MODULE_GAS
> +/* SGI/MIPS specific extensions.  */
> +#ifdef TC_MIPS
>  DW_CFA (DW_CFA_MIPS_advance_loc8, 0x1d)
> -/* AArch64 extensions.  */
> +#endif /* TC_MIPS */
> +
> +/* AArch64 specific extensions.  */
> +#ifdef TC_AARCH64
>  DW_CFA (DW_CFA_AARCH64_negate_ra_state_with_pc, 0x2c)
> -/* GNU extensions.
> -   NOTE: DW_CFA_GNU_window_save is multiplexed on Sparc and AArch64.  */
> +DW_CFA (DW_CFA_AARCH64_negate_ra_state, 0x2d)
> +/* NOTE: DW_CFA_GNU_window_save is multiplexed on Sparc and AArch64.  */
> +DW_CFA_DUP (DW_CFA_GNU_window_save, 0x2d)
> +#endif /* TC_AARCH64 */
> +
> +/* Sparc specific extensions.  */
> +#ifdef TC_SPARC
>  DW_CFA (DW_CFA_GNU_window_save, 0x2d)
> -DW_CFA_DUP (DW_CFA_AARCH64_negate_ra_state, 0x2d)
> +#endif /* TC_SPARC */
> +#else
> +/* SGI/MIPS specific extensions.  */
> +DW_CFA (DW_CFA_MIPS_advance_loc8, 0x1d)
> +/* AArch64 specific extensions.  */
> +DW_CFA (DW_CFA_AARCH64_negate_ra_state_with_pc, 0x2c)
> +DW_CFA (DW_CFA_AARCH64_negate_ra_state, 0x2d)
> +/* NOTE: DW_CFA_GNU_window_save is multiplexed on Sparc and AArch64.  */
> +DW_CFA_DUP (DW_CFA_GNU_window_save, 0x2d)
> +#endif

Does this really need doing with nested #if/#else? I think it would be easier
to follow if each DW_CFA_* appeared just once here. And whether ...

> +/* GNU extensions.  */
>  DW_CFA (DW_CFA_GNU_args_size, 0x2e)
>  DW_CFA (DW_CFA_GNU_negative_offset_extended, 0x2f)
>  
> +DW_CFA (DW_CFA_hi_user, 0x3f)

... this really needs to move I'm also unsure.

Jan
  

Patch

diff --git a/bfd/elf-eh-frame.c b/bfd/elf-eh-frame.c
index 78b3ecb5a7d..cd2b5f03346 100644
--- a/bfd/elf-eh-frame.c
+++ b/bfd/elf-eh-frame.c
@@ -338,12 +338,79 @@  next_cie_fde_offset (const struct eh_cie_fde *ent,
   return sec->size;
 }
 
+static bool
+skip_cfa_op_aarch64 (bfd_byte op,
+		     bfd_byte **iter ATTRIBUTE_UNUSED,
+		     bfd_byte *end ATTRIBUTE_UNUSED)
+{
+  switch (op)
+    {
+      case DW_CFA_AARCH64_negate_ra_state:
+      case DW_CFA_AARCH64_negate_ra_state_with_pc:
+	/* No arguments.  */
+	return true;
+
+      default:
+	return false;
+    }
+}
+
+static bool
+skip_cfa_op_mips (bfd_byte op,
+		  bfd_byte **iter,
+		  bfd_byte *end)
+{
+  switch (op)
+    {
+      case DW_CFA_MIPS_advance_loc8:
+	return skip_bytes (iter, end, 8);
+
+      default:
+	return false;
+    }
+}
+
+static bool
+skip_cfa_op_sparc (bfd_byte op,
+		   bfd_byte **iter ATTRIBUTE_UNUSED,
+		   bfd_byte *end ATTRIBUTE_UNUSED)
+{
+  switch (op)
+    {
+      case DW_CFA_GNU_window_save:
+	/* No arguments.  */
+	return true;
+
+      default:
+	return false;
+    }
+}
+
+static bool
+skip_cfa_op_arch_specific (enum bfd_architecture arch,
+			   bfd_byte **iter, bfd_byte *end,
+			   bfd_byte op)
+{
+  switch (arch)
+    {
+      case bfd_arch_aarch64:
+	return skip_cfa_op_aarch64 (op, iter, end);
+      case bfd_arch_mips:
+	return skip_cfa_op_mips (op, iter, end);
+      case bfd_arch_sparc:
+	return skip_cfa_op_sparc (op, iter, end);
+      default:
+	return false;
+    }
+}
+
 /* Assume that the bytes between *ITER and END are CFA instructions.
    Try to move *ITER past the first instruction and return true on
    success.  ENCODED_PTR_WIDTH gives the width of pointer entries.  */
 
 static bool
-skip_cfa_op (bfd_byte **iter, bfd_byte *end, unsigned int encoded_ptr_width)
+skip_cfa_op (enum bfd_architecture arch, bfd_byte **iter, bfd_byte *end,
+	     unsigned int encoded_ptr_width)
 {
   bfd_byte op = 0;
   bfd_vma length;
@@ -351,15 +418,14 @@  skip_cfa_op (bfd_byte **iter, bfd_byte *end, unsigned int encoded_ptr_width)
   if (!read_byte (iter, end, &op))
     return false;
 
-  switch (op & 0xc0 ? op & 0xc0 : op)
+  op = op & 0xc0 ? op & 0xc0 : op;
+  switch (op)
     {
     case DW_CFA_nop:
     case DW_CFA_advance_loc:
     case DW_CFA_restore:
     case DW_CFA_remember_state:
     case DW_CFA_restore_state:
-    case DW_CFA_GNU_window_save:
-    case DW_CFA_AARCH64_negate_ra_state_with_pc:
       /* No arguments.  */
       return true;
 
@@ -410,10 +476,9 @@  skip_cfa_op (bfd_byte **iter, bfd_byte *end, unsigned int encoded_ptr_width)
     case DW_CFA_advance_loc4:
       return skip_bytes (iter, end, 4);
 
-    case DW_CFA_MIPS_advance_loc8:
-      return skip_bytes (iter, end, 8);
-
     default:
+      if (op >= DW_CFA_lo_user && op <= DW_CFA_hi_user)
+	return skip_cfa_op_arch_specific (arch, iter, end, op);
       return false;
     }
 }
@@ -424,8 +489,8 @@  skip_cfa_op (bfd_byte **iter, bfd_byte *end, unsigned int encoded_ptr_width)
    ENCODED_PTR_WIDTH is as for skip_cfa_op.  */
 
 static bfd_byte *
-skip_non_nops (bfd_byte *buf, bfd_byte *end, unsigned int encoded_ptr_width,
-	       unsigned int *set_loc_count)
+skip_non_nops (enum bfd_architecture arch, bfd_byte *buf, bfd_byte *end,
+	       unsigned int encoded_ptr_width, unsigned int *set_loc_count)
 {
   bfd_byte *last;
 
@@ -437,7 +502,7 @@  skip_non_nops (bfd_byte *buf, bfd_byte *end, unsigned int encoded_ptr_width,
       {
 	if (*buf == DW_CFA_set_loc)
 	  ++*set_loc_count;
-	if (!skip_cfa_op (&buf, end, encoded_ptr_width))
+	if (!skip_cfa_op (arch, &buf, end, encoded_ptr_width))
 	  return 0;
 	last = buf;
       }
@@ -994,7 +1059,8 @@  _bfd_elf_parse_eh_frame (bfd *abfd, struct bfd_link_info *info,
 	 include the padding.  */
       length = get_DW_EH_PE_width (cie->fde_encoding, ptr_size);
       set_loc_count = 0;
-      insns_end = skip_non_nops (insns, end, length, &set_loc_count);
+      insns_end = skip_non_nops (bfd_get_arch (abfd), insns, end, length,
+				 &set_loc_count);
       /* If we don't understand the CFA instructions, we can't know
 	 what needs to be adjusted there.  */
       if (insns_end == NULL
@@ -1025,7 +1091,7 @@  _bfd_elf_parse_eh_frame (bfd *abfd, struct bfd_link_info *info,
 	    {
 	      if (*p == DW_CFA_set_loc)
 		this_inf->set_loc[++cnt] = p + 1 - start;
-	      REQUIRE (skip_cfa_op (&p, end, length));
+	      REQUIRE (skip_cfa_op (bfd_get_arch (abfd), &p, end, length));
 	    }
 	}
 
diff --git a/gas/config.in b/gas/config.in
index bb896244640..4f58c40d35b 100644
--- a/gas/config.in
+++ b/gas/config.in
@@ -173,6 +173,9 @@ 
 /* Choose a default ABI for MIPS targets. */
 #undef MIPS_DEFAULT_ABI
 
+/* Building gas module */
+#undef MODULE_GAS
+
 /* Define value for nds32_arch_name */
 #undef NDS32_DEFAULT_ARCH_NAME
 
diff --git a/gas/configure b/gas/configure
index 16a8937f53a..b4fbc7922e4 100755
--- a/gas/configure
+++ b/gas/configure
@@ -12993,6 +12993,10 @@  cat >>confdefs.h <<_ACEOF
 _ACEOF
 
 
+
+$as_echo "#define MODULE_GAS 1" >>confdefs.h
+
+
 for ac_prog in 'bison -y' byacc
 do
   # Extract the first word of "$ac_prog", so it can be a program name with args.
diff --git a/gas/configure.ac b/gas/configure.ac
index e97ec598b6f..d81746542e8 100644
--- a/gas/configure.ac
+++ b/gas/configure.ac
@@ -937,6 +937,8 @@  AC_DEFINE_UNQUOTED(TARGET_CPU,		"${target_cpu}",   [Target CPU.])
 AC_DEFINE_UNQUOTED(TARGET_VENDOR,	"${target_vendor}", [Target vendor.])
 AC_DEFINE_UNQUOTED(TARGET_OS,		"${target_os}",    [Target OS.])
 
+AC_DEFINE(MODULE_GAS, 1, [Building gas module])
+
 AC_PROG_YACC
 AM_PROG_LEX
 
diff --git a/gas/dw2gencfi.c b/gas/dw2gencfi.c
index a21ae322488..d025da35676 100644
--- a/gas/dw2gencfi.c
+++ b/gas/dw2gencfi.c
@@ -720,9 +720,16 @@  const pseudo_typeS cfi_pseudo_table[] =
     { "cfi_same_value", dot_cfi, DW_CFA_same_value },
     { "cfi_remember_state", dot_cfi, DW_CFA_remember_state },
     { "cfi_restore_state", dot_cfi, DW_CFA_restore_state },
-    { "cfi_window_save", dot_cfi, DW_CFA_GNU_window_save },
+#if TC_AARCH64
+    /* cfi_window_save is an alias of cfi_negate_ra_state which is kept for
+       backward-compatibility concerns.  */
+    { "cfi_window_save", dot_cfi, DW_CFA_AARCH64_negate_ra_state },
     { "cfi_negate_ra_state", dot_cfi, DW_CFA_AARCH64_negate_ra_state },
     { "cfi_negate_ra_state_with_pc", dot_cfi, DW_CFA_AARCH64_negate_ra_state_with_pc },
+#endif /* TC_AARCH64 */
+#if TC_SPARC
+    { "cfi_window_save", dot_cfi, DW_CFA_GNU_window_save },
+#endif /* TC_SPARC */
     { "cfi_escape", dot_cfi_escape, 0 },
     { "cfi_signal_frame", dot_cfi, CFI_signal_frame },
     { "cfi_personality", dot_cfi_personality, 0 },
@@ -919,13 +926,21 @@  dot_cfi (int arg)
       cfi_add_CFA_restore_state ();
       break;
 
-    case DW_CFA_GNU_window_save:
-      cfi_add_CFA_insn (DW_CFA_GNU_window_save);
+#if TC_AARCH64
+    case DW_CFA_AARCH64_negate_ra_state:
+      cfi_add_CFA_insn (DW_CFA_AARCH64_negate_ra_state);
       break;
 
     case DW_CFA_AARCH64_negate_ra_state_with_pc:
       cfi_add_CFA_insn (DW_CFA_AARCH64_negate_ra_state_with_pc);
       break;
+#endif /* TC_AARCH64 */
+
+#if TC_SPARC
+    case DW_CFA_GNU_window_save:
+      cfi_add_CFA_insn (DW_CFA_GNU_window_save);
+      break;
+#endif /* TC_SPARC */
 
     case CFI_signal_frame:
       frchain_now->frch_cfi_data->cur_fde_data->signal_frame = 1;
@@ -1824,13 +1839,21 @@  output_cfi_insn (struct cfi_insn_data *insn)
       out_one (insn->insn);
       break;
 
-    case DW_CFA_GNU_window_save:
-      out_one (DW_CFA_GNU_window_save);
+#if TC_AARCH64
+    case DW_CFA_AARCH64_negate_ra_state:
+      out_one (DW_CFA_AARCH64_negate_ra_state);
       break;
 
     case DW_CFA_AARCH64_negate_ra_state_with_pc:
       out_one (DW_CFA_AARCH64_negate_ra_state_with_pc);
       break;
+#endif /* TC_AARCH64 */
+
+#if TC_SPARC
+    case DW_CFA_GNU_window_save:
+      out_one (DW_CFA_GNU_window_save);
+      break;
+#endif /* TC_SPARC */
 
     case CFI_escape:
       {
diff --git a/gas/gen-sframe.c b/gas/gen-sframe.c
index 5b5e7b14b3a..3a5ecda47de 100644
--- a/gas/gen-sframe.c
+++ b/gas/gen-sframe.c
@@ -1257,6 +1257,8 @@  sframe_xlate_do_restore (struct sframe_xlate_ctx *xlate_ctx,
   return SFRAME_XLATE_OK;
 }
 
+#if TC_AARCH64
+
 /* Translate DW_CFA_AARCH64_negate_ra_state into SFrame context.
    Return SFRAME_XLATE_OK if success.  */
 
@@ -1287,30 +1289,7 @@  sframe_xlate_do_aarch64_negate_ra_state_with_pc (struct sframe_xlate_ctx *xlate_
   return SFRAME_XLATE_ERR_NOTREPRESENTED;  /* Not represented.  */
 }
 
-/* Translate DW_CFA_GNU_window_save into SFrame context.
-   DW_CFA_GNU_window_save is a DWARF Sparc extension, but is multiplexed with a
-   directive of DWARF AArch64 extension: DW_CFA_AARCH64_negate_ra_state.
-   The AArch64 backend of GCC 14 and older versions was emitting mistakenly the
-   Sparc CFI directive (.cfi_window_save).  From GCC 15, the AArch64 backend
-   only emits .cfi_negate_ra_state.  For backward compatibility, the handler for
-   .cfi_window_save needs to check whether the directive was used in a AArch64
-   ABI context or not.
-   Return SFRAME_XLATE_OK if success.  */
-
-static int
-sframe_xlate_do_gnu_window_save (struct sframe_xlate_ctx *xlate_ctx,
-				 struct cfi_insn_data *cfi_insn)
-{
-  unsigned char abi_arch = sframe_get_abi_arch ();
-
-  /* Translate DW_CFA_AARCH64_negate_ra_state into SFrame context.  */
-  if (abi_arch == SFRAME_ABI_AARCH64_ENDIAN_BIG
-      || abi_arch == SFRAME_ABI_AARCH64_ENDIAN_LITTLE)
-    return sframe_xlate_do_aarch64_negate_ra_state (xlate_ctx, cfi_insn);
-
-  as_warn (_("skipping SFrame FDE; .cfi_window_save"));
-  return SFRAME_XLATE_ERR_NOTREPRESENTED;  /* Not represented.  */
-}
+#endif /* TC_AARCH64 */
 
 /* Handle DW_CFA_expression in .cfi_escape.
 
@@ -1622,14 +1601,14 @@  sframe_do_cfi_insn (struct sframe_xlate_ctx *xlate_ctx,
     case DW_CFA_restore:
       err = sframe_xlate_do_restore (xlate_ctx, cfi_insn);
       break;
-    /* DW_CFA_AARCH64_negate_ra_state is multiplexed with
-       DW_CFA_GNU_window_save.  */
-    case DW_CFA_GNU_window_save:
-      err = sframe_xlate_do_gnu_window_save (xlate_ctx, cfi_insn);
+#if TC_AARCH64
+    case DW_CFA_AARCH64_negate_ra_state:
+      err = sframe_xlate_do_aarch64_negate_ra_state (xlate_ctx, cfi_insn);
       break;
     case DW_CFA_AARCH64_negate_ra_state_with_pc:
       err = sframe_xlate_do_aarch64_negate_ra_state_with_pc (xlate_ctx, cfi_insn);
       break;
+#endif /* TC_AARCH64 */
     case DW_CFA_register:
       err = sframe_xlate_do_register (xlate_ctx, cfi_insn);
       break;
diff --git a/include/dwarf2.def b/include/dwarf2.def
index 6d6c0e0cc39..c9dd3e4aae3 100644
--- a/include/dwarf2.def
+++ b/include/dwarf2.def
@@ -780,20 +780,46 @@  DW_CFA (DW_CFA_val_offset, 0x14)
 DW_CFA (DW_CFA_val_offset_sf, 0x15)
 DW_CFA (DW_CFA_val_expression, 0x16)
 
+/* Users extensions.
+   Note: DW_CFA_lo_user and DW_CFA_hi_user can be aliased to used as extension.
+   This is a closed interval.  */
 DW_CFA (DW_CFA_lo_user, 0x1c)
-DW_CFA (DW_CFA_hi_user, 0x3f)
 
-/* SGI/MIPS specific.  */
+/* For gas, we disable the extensions according to the target architecture.  */
+#ifdef MODULE_GAS
+/* SGI/MIPS specific extensions.  */
+#ifdef TC_MIPS
 DW_CFA (DW_CFA_MIPS_advance_loc8, 0x1d)
-/* AArch64 extensions.  */
+#endif /* TC_MIPS */
+
+/* AArch64 specific extensions.  */
+#ifdef TC_AARCH64
 DW_CFA (DW_CFA_AARCH64_negate_ra_state_with_pc, 0x2c)
-/* GNU extensions.
-   NOTE: DW_CFA_GNU_window_save is multiplexed on Sparc and AArch64.  */
+DW_CFA (DW_CFA_AARCH64_negate_ra_state, 0x2d)
+/* NOTE: DW_CFA_GNU_window_save is multiplexed on Sparc and AArch64.  */
+DW_CFA_DUP (DW_CFA_GNU_window_save, 0x2d)
+#endif /* TC_AARCH64 */
+
+/* Sparc specific extensions.  */
+#ifdef TC_SPARC
 DW_CFA (DW_CFA_GNU_window_save, 0x2d)
-DW_CFA_DUP (DW_CFA_AARCH64_negate_ra_state, 0x2d)
+#endif /* TC_SPARC */
+#else
+/* SGI/MIPS specific extensions.  */
+DW_CFA (DW_CFA_MIPS_advance_loc8, 0x1d)
+/* AArch64 specific extensions.  */
+DW_CFA (DW_CFA_AARCH64_negate_ra_state_with_pc, 0x2c)
+DW_CFA (DW_CFA_AARCH64_negate_ra_state, 0x2d)
+/* NOTE: DW_CFA_GNU_window_save is multiplexed on Sparc and AArch64.  */
+DW_CFA_DUP (DW_CFA_GNU_window_save, 0x2d)
+#endif
+
+/* GNU extensions.  */
 DW_CFA (DW_CFA_GNU_args_size, 0x2e)
 DW_CFA (DW_CFA_GNU_negative_offset_extended, 0x2f)
 
+DW_CFA (DW_CFA_hi_user, 0x3f)
+
 DW_END_CFA
 
 /* Index attributes in the Abbreviations Table.  */