[v7,3/3] aarch64: Implement Structured Exception Handling (SEH) on AArch64

Message ID 20260319111638.93074-4-evgeny.karpov@arm.com
State New
Headers
Series Implement Structured Exception Handling (SEH) on AArch64 |

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-aarch64 success Test passed
linaro-tcwg-bot/tcwg_binutils_check--master-arm success Test passed

Commit Message

Evgeny Karpov March 19, 2026, 11:16 a.m. UTC
  The patch reuses shared helpers for SEH and implements SEH on AArch64.
The implementation is based on
(https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170)
and pdata/xdata SEH records are emitted from md_finish.

When .pdata/.xdata is emitted, the function size is required.
Function sizes are calculated as late as possible, and the code segment needs
to be relaxed to be able to calculate the function sizes.

Initially, obj_coff_generate_pdata was declared in write_object_file.
Before the change, obj_coff_generate_pdata was used only to validate
syntax, which was sufficient for that purpose. However, that location
seems incorrect, as it is too late to emit .pdata/.xdata records
in the AArch64 case.

md_finish has been declared for AArch64 and extended with
seh_aarch64_write_data to emit .pdata/.xdata records after all
assembly has been completed.

Signed-off-by: Evgeny Karpov <evgeny@kmaps.co>

gas/ChangeLog:
	* gas/config/obj-coff-seh-shared.c (defined): Update.
	* gas/config/obj-coff.c (defined): Update.
	* gas/config/tc-aarch64.c (defined): Add OBJ_COFF guard.
	(aarch64_md_finish): Add.
	* gas/config/tc-aarch64.h (defined): Add OBJ_COFF guard.
	(md_finish): Add.
	(aarch64_md_finish): Add.
	(seh_aarch64_write_data): Add.
	* gas/write.c: Update.
	* gas/write.h (subsegs_finish_section): Update.
	* gas/config/obj-coff-seh-aarch64.c: New file.
	* gas/config/obj-coff-seh-aarch64.h: New file.
---
 gas/config/obj-coff-seh-aarch64.c | 889 ++++++++++++++++++++++++++++++
 gas/config/obj-coff-seh-aarch64.h | 265 +++++++++
 gas/config/obj-coff-seh-shared.c  |   5 +
 gas/config/obj-coff.c             |   4 +
 gas/config/tc-aarch64.c           |  10 +
 gas/config/tc-aarch64.h           |   6 +
 gas/write.c                       |   2 +-
 gas/write.h                       |   1 +
 8 files changed, 1181 insertions(+), 1 deletion(-)
 create mode 100644 gas/config/obj-coff-seh-aarch64.c
 create mode 100644 gas/config/obj-coff-seh-aarch64.h
  

Comments

Saurabh Jha March 26, 2026, 4:57 p.m. UTC | #1
On 3/19/2026 11:16 AM, Evgeny Karpov wrote:
> The patch reuses shared helpers for SEH and implements SEH on AArch64.
> The implementation is based on
> (https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170)
> and pdata/xdata SEH records are emitted from md_finish.
>
> When .pdata/.xdata is emitted, the function size is required.
> Function sizes are calculated as late as possible, and the code segment needs
> to be relaxed to be able to calculate the function sizes.
>
> Initially, obj_coff_generate_pdata was declared in write_object_file.
> Before the change, obj_coff_generate_pdata was used only to validate
> syntax, which was sufficient for that purpose. However, that location
> seems incorrect, as it is too late to emit .pdata/.xdata records
> in the AArch64 case.
>
> md_finish has been declared for AArch64 and extended with
> seh_aarch64_write_data to emit .pdata/.xdata records after all
> assembly has been completed.
>
> Signed-off-by: Evgeny Karpov <evgeny@kmaps.co>
>
> gas/ChangeLog:
> 	* gas/config/obj-coff-seh-shared.c (defined): Update.
> 	* gas/config/obj-coff.c (defined): Update.
> 	* gas/config/tc-aarch64.c (defined): Add OBJ_COFF guard.
> 	(aarch64_md_finish): Add.
> 	* gas/config/tc-aarch64.h (defined): Add OBJ_COFF guard.
> 	(md_finish): Add.
> 	(aarch64_md_finish): Add.
> 	(seh_aarch64_write_data): Add.
> 	* gas/write.c: Update.
> 	* gas/write.h (subsegs_finish_section): Update.
> 	* gas/config/obj-coff-seh-aarch64.c: New file.
> 	* gas/config/obj-coff-seh-aarch64.h: New file.
> ---
>   gas/config/obj-coff-seh-aarch64.c | 889 ++++++++++++++++++++++++++++++
>   gas/config/obj-coff-seh-aarch64.h | 265 +++++++++
>   gas/config/obj-coff-seh-shared.c  |   5 +
>   gas/config/obj-coff.c             |   4 +
>   gas/config/tc-aarch64.c           |  10 +
>   gas/config/tc-aarch64.h           |   6 +
>   gas/write.c                       |   2 +-
>   gas/write.h                       |   1 +
>   8 files changed, 1181 insertions(+), 1 deletion(-)
>   create mode 100644 gas/config/obj-coff-seh-aarch64.c
>   create mode 100644 gas/config/obj-coff-seh-aarch64.h
>
> diff --git a/gas/config/obj-coff-seh-aarch64.c b/gas/config/obj-coff-seh-aarch64.c
> new file mode 100644
> index 00000000000..6b5354f6544
> --- /dev/null
> +++ b/gas/config/obj-coff-seh-aarch64.c
> @@ -0,0 +1,889 @@
> +/* SEH .pdata/.xdata COFF object file format on AArch64
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +
> +   This file is part of GAS.
> +
> +   GAS is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3, or (at your option)
> +   any later version.
> +
> +   GAS is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with GAS; see the file COPYING.  If not, write to the Free
> +   Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
> +   02110-1301, USA.  */
> +
> +#include "obj-coff-seh-aarch64.h"
> +
> +static struct seh_aarch64_context *seh_ctx_root = NULL;
> +static bool in_seh_proc = false;
> +
> +struct aarch64_unwind_code_pack_info {
> +  const char *directive;
> +  unsigned offset_bits;
> +  unsigned reg_bits;
> +  unsigned code_bits;
> +  unsigned code;
> +  unsigned offset_right_shift;
> +  unsigned offset;
> +  unsigned reg_right_shift;
> +  unsigned reg_offset;
> +  unsigned size;
> +};
> +
> +static const struct aarch64_unwind_code_pack_info
> +aarch64_unwind_code_pack_data[] = {
> +/* Unwind codes packing for AArch64 is described at
> +   https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170#unwind-codes
> +   and calculated in seh_aarch64_add_unwind_element function.  */
> +  {
> +    .directive = NULL, .offset_bits = 5, .reg_bits = 0,
> +    .code_bits = 3, .code = AARCH64_UNOP_ALLOCS, .offset_right_shift = 4,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },
> +  {
> +    .directive = NULL, .offset_bits = 11, .reg_bits = 0,
> +    .code_bits = 5, .code = AARCH64_UNOP_ALLOCM, .offset_right_shift = 4,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 2
> +  },
> +  {
> +    .directive = NULL, .offset_bits = 24, .reg_bits = 0,
> +    .code_bits = 8, .code = AARCH64_UNOP_ALLOCL, .offset_right_shift = 4,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 4
> +  },
> +  {
> +    .directive = ".seh_save_reg", .offset_bits = 6, .reg_bits = 4,
> +    .code_bits = 6, .code = AARCH64_UNOP_SAVEREG, .offset_right_shift = 3,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 19, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_reg_x", .offset_bits = 5, .reg_bits = 4,
> +    .code_bits = 7, .code = AARCH64_UNOP_SAVEREGX, .offset_right_shift = 3,
> +    .offset = 1, .reg_right_shift = 0, .reg_offset = 19, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_regp", .offset_bits = 6, .reg_bits = 4,
> +    .code_bits = 6, .code = AARCH64_UNOP_SAVEREGP, .offset_right_shift = 3,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 19, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_regp_x", .offset_bits = 6, .reg_bits = 4,
> +    .code_bits = 6, .code = AARCH64_UNOP_SAVEREGPX, .offset_right_shift = 3,
> +    .offset = 1, .reg_right_shift = 0, .reg_offset = 19, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_fregp", .offset_bits = 6, .reg_bits = 3,
> +    .code_bits = 7, .code = AARCH64_UNOP_SAVEFREGP, .offset_right_shift = 3,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 8, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_fregp_x", .offset_bits = 6, .reg_bits = 3,
> +    .code_bits = 7, .code = AARCH64_UNOP_SAVEFREGPX, .offset_right_shift = 3,
> +    .offset = 1, .reg_right_shift = 0, .reg_offset = 8, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_freg", .offset_bits = 6, .reg_bits = 3,
> +    .code_bits = 7, .code = AARCH64_UNOP_SAVEFREG, .offset_right_shift = 3,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 8, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_freg_x", .offset_bits = 5, .reg_bits = 3,
> +    .code_bits = 8, .code = AARCH64_UNOP_SAVEFREGX, .offset_right_shift = 3,
> +    .offset = 1, .reg_right_shift = 0, .reg_offset = 8, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_lrpair", .offset_bits = 6, .reg_bits = 3,
> +    .code_bits = 7, .code = AARCH64_UNOP_SAVELRPAIR, .offset_right_shift = 3,
> +    .offset = 0, .reg_right_shift = 1, .reg_offset = 19, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_fplr", .offset_bits = 6, .reg_bits = 0,
> +    .code_bits = 2, .code = AARCH64_UNOP_SAVEFPLR, .offset_right_shift = 3,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },
> +  {
> +    .directive = ".seh_save_fplr_x", .offset_bits = 6, .reg_bits = 0,
> +    .code_bits = 2, .code = AARCH64_UNOP_SAVEFPLRX, .offset_right_shift = 3,
> +    .offset = 1, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },
> +  {
> +    .directive = ".seh_save_r19r20_x", .offset_bits = 5, .reg_bits = 0,
> +    .code_bits = 3, .code = AARCH64_UNOP_SAVER19R20X, .offset_right_shift = 3,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },
> +  {
> +    .directive = ".seh_add_fp",	.offset_bits = 8, .reg_bits = 0,
> +    .code_bits = 8, .code = AARCH64_UNOP_ADDFP,	.offset_right_shift = 0,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 2
> +  },
> +  {
> +    .directive = ".seh_set_fp",	.offset_bits = 0, .reg_bits = 0,
> +    .code_bits = 8, .code = AARCH64_UNOP_SETFP,	.offset_right_shift = 0,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },
> +  {
> +    .directive = ".seh_save_next", .offset_bits = 0, .reg_bits = 0,
> +    .code_bits = 8, .code = AARCH64_UNOP_SAVENEXT, .offset_right_shift = 0,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },
> +  {
> +    .directive = ".seh_nop", .offset_bits = 0, .reg_bits = 0,
> +    .code_bits = 8, .code = AARCH64_UNOP_NOP, .offset_right_shift = 0,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },
> +  {
> +    .directive = ".seh_pac_sign_lr", .offset_bits = 0, .reg_bits = 0,
> +    .code_bits = 8, .code = AARCH64_UNOP_PACSIGNLR, .offset_right_shift = 0,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },
> +  {
> +    .directive = NULL, .offset_bits = 0, .reg_bits = 0,
> +    .code_bits = 8, .code = AARCH64_UNOP_END, .offset_right_shift = 0,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },
> +};
> +
> +/* Set for current context the default handler.  */
> +static void
> +obj_coff_seh_handler (const int what ATTRIBUTE_UNUSED)
> +{
> +  char *symbol_name;
> +  char name_end;
> +
> +  if (!verify_context (".seh_handler"))
> +    return;
> +
> +  if (*input_line_pointer == 0 || *input_line_pointer == '\n')
> +    as_bad (_(".seh_handler requires a handler"));
> +
> +  SKIP_WHITESPACE ();
> +
> +  if (*input_line_pointer == '@')
> +    {
> +      name_end = get_symbol_name (&symbol_name);
> +
> +      seh_ctx_cur->handler.X_op = O_constant;
> +      seh_ctx_cur->handler.X_add_number = 0;
> +
> +      if (strcasecmp (symbol_name, "@0") == 0
> +	  || strcasecmp (symbol_name, "@null") == 0)
> +	;
If we're not doing anything in this if body, why do we have it? Is it to 
be able to write the bottom conditionals in an easier way?yhn
> +      else if (strcasecmp (symbol_name, "@1") == 0)
> +	seh_ctx_cur->handler.X_add_number = 1;
> +      else
> +	as_bad (_("unknown constant value '%s' for handler"), symbol_name);
> +
> +      (void) restore_line_pointer (name_end);
> +    }
> +  else
> +    expression (&seh_ctx_cur->handler);
> +
> +  seh_ctx_cur->handler_data.X_op = O_constant;
> +  seh_ctx_cur->handler_data.X_add_number = 0;
> +  seh_ctx_cur->xdata_header.x = 1;
> +
> +  while (skip_whitespace_and_comma (0))
> +    {
> +      name_end = get_symbol_name (&symbol_name);
> +      (void) restore_line_pointer (name_end);
> +    }
> +}
> +
> +/* Switch to subsection for handler data for exception region.  */
> +static void
> +obj_coff_seh_handlerdata (const int what ATTRIBUTE_UNUSED)
> +{
> +  demand_empty_rest_of_line ();
> +
> +  switch_xdata (seh_ctx_cur->subsection + 1, seh_ctx_cur->code_seg);
> +}
> +
> +/* Obtain available unwind element.  */
> +static void
> +seh_aarch64_add_unwind_element (const seh_aarch64_unwind_types unwind_type,
> +				unsigned offset, unsigned reg)
> +{
> +  gas_assert (in_seh_proc);
> +
> +  const struct aarch64_unwind_code_pack_info *unwind_code_pack_info
> +    = aarch64_unwind_code_pack_data + unwind_type;
> +  unsigned value_offset_bits = 0;
> +
> +  if ((seh_ctx_cur->unwind_codes_byte_count
> +      + unwind_code_pack_info->size) > AARCH64_MAX_UNWIND_CODES_SIZE)
> +    as_bad (_("no unwind element available."));
> +
> +  seh_aarch64_unwind_code *aarch64_element;
> +  aarch64_element = seh_ctx_cur->unwind_codes
> +		    + seh_ctx_cur->unwind_codes_count++;
> +  aarch64_element->value = 0;
> +
> +  if (unwind_code_pack_info->offset_bits)
> +    {
> +      offset = (offset >> unwind_code_pack_info->offset_right_shift)
> +	       - unwind_code_pack_info->offset;
> +      offset &= (1 << unwind_code_pack_info->offset_bits) - 1;
> +      aarch64_element->value |= offset << value_offset_bits;
> +      value_offset_bits += unwind_code_pack_info->offset_bits;
> +    }
> +
> +  if (unwind_code_pack_info->reg_bits)
> +    {
> +      reg = (reg >> unwind_code_pack_info->reg_right_shift)
> +	    - unwind_code_pack_info->reg_offset;
> +      reg &= (1 << unwind_code_pack_info->reg_bits) - 1;
> +      aarch64_element->value |= reg << value_offset_bits;
> +      value_offset_bits += unwind_code_pack_info->reg_bits;
> +    }
> +
> +  if (unwind_code_pack_info->code_bits)
> +    {
> +      unsigned code = unwind_code_pack_info->code;
> +      code &= (1 << unwind_code_pack_info->code_bits) - 1;
> +      aarch64_element->value |= code << value_offset_bits;
> +    }
> +
> +  aarch64_element->type = unwind_type;
> +  seh_ctx_cur->unwind_codes_byte_count += unwind_code_pack_info->size;
> +}
> +
> +/* Mark begin of new context.  */
> +static void
> +obj_coff_seh_proc (const int what ATTRIBUTE_UNUSED)
> +{
> +  char *symbol_name;
> +  char name_end;
> +
> +  if (in_seh_proc)
> +    as_bad (_("previous SEH entry not closed (missing .seh_endproc)"));
> +
> +  if (*input_line_pointer == 0 || *input_line_pointer == '\n')
> +    as_bad (_(".seh_proc requires function label name"));
> +
> +
> +  if (!seh_ctx_root)
> +  {
> +    seh_ctx_root = XCNEW (seh_context);
> +    seh_ctx_cur = seh_ctx_root;
> +  }
> +  else
> +  {
> +    seh_ctx_cur->next = XCNEW (seh_context);
> +    seh_ctx_cur = seh_ctx_cur->next;
> +  }
> +
> +  seh_ctx_cur->next = NULL;
> +  seh_ctx_cur->code_seg = now_seg;
> +
> +  /* The current implementation always use a pair of .pdata and .xdata
> +     records.  */
> +  const bool use_xdata = true;
> +
> +  if (use_xdata)
> +    {
> +      x_segcur = seh_hash_find_or_make (seh_ctx_cur->code_seg, ".xdata");
> +      seh_ctx_cur->subsection = x_segcur->subseg;
> +      x_segcur->subseg += 2;
> +
> +      /* Initialize an empty .xdata record.  */
> +      seh_ctx_cur->unwind_codes_count = 0;
> +      seh_ctx_cur->unwind_codes_byte_count = 0;
> +      seh_ctx_cur->epilogue_scopes_count = 0;
> +      seh_ctx_cur->epilogue_scopes_capacity = 0;
> +      seh_ctx_cur->epilogue_scopes = NULL;
> +      seh_ctx_cur->xdata_header.x = 0;
> +    }
> +
> +  SKIP_WHITESPACE ();
> +
> +  name_end = get_symbol_name (&symbol_name);
> +  seh_ctx_cur->func_name = xstrdup (symbol_name);
> +  (void) restore_line_pointer (name_end);
> +
> +  demand_empty_rest_of_line ();
> +
> +  seh_ctx_cur->start_addr = symbol_temp_new_now ();
> +  in_seh_proc = true;
> +}
> +
> +/* Mark end of prologue for current context.  */
> +static void
> +obj_coff_seh_endprologue (const int what ATTRIBUTE_UNUSED)
> +{
> +  if (!verify_context (".seh_endprologue")
> +      || !seh_validate_seg (".seh_endprologue"))
> +    return;
> +  demand_empty_rest_of_line ();
> +
> +  if (seh_ctx_cur->endprologue_addr != NULL)
> +    as_warn (_("duplicate .seh_endprologue in .seh_proc block"));
> +  else
> +    seh_ctx_cur->endprologue_addr = symbol_temp_new_now ();
> +
> +  /* Unwind codes need to be reversed.  */
> +  for (unsigned i = 0, n = seh_ctx_cur->unwind_codes_count; i < n / 2; ++i)
> +    {
> +      seh_aarch64_unwind_code *unwind_codes = seh_ctx_cur->unwind_codes;
> +      const seh_aarch64_unwind_code temp = unwind_codes[i];
> +      unwind_codes[i] = unwind_codes[n-i-1];
> +      unwind_codes[n-i-1] = temp;
> +    }
> +
> +   seh_aarch64_add_unwind_element (unwind_end, 0, 0);
> +}
> +
> +/* Mark end of current context.  */
> +static void
> +obj_coff_seh_endproc (const int what ATTRIBUTE_UNUSED)
> +{
> +  demand_empty_rest_of_line ();
> +  if (!in_seh_proc)
> +    as_bad (_(".seh_endproc used without .seh_proc"));
> +
> +  seh_validate_seg (".seh_endproc");
> +
> +  seh_ctx_cur->end_addr = symbol_temp_new_now ();
> +  in_seh_proc = false;
> +}
> +
> +static void
> +obj_coff_seh_startepilogue (const int what ATTRIBUTE_UNUSED)
> +{
> +  if (!verify_context (".seh_startepilogue")
> +      || !seh_validate_seg (".seh_startepilogue"))
> +    return;
> +  demand_empty_rest_of_line ();
> +
> +  const unsigned max_epilogue_scopes = AARCH64_MAX_EPILOGUE_SCOPES;
> +  if (seh_ctx_cur->epilogue_scopes_count >= max_epilogue_scopes)
> +    as_bad (_("no epilogue scopes available."));
> +
> +  symbolS *epilogue_start_addr = symbol_temp_new_now ();
> +  expressionS exp;
> +  exp.X_op = O_subtract;
> +  exp.X_add_symbol = epilogue_start_addr;
> +  exp.X_op_symbol = seh_ctx_cur->start_addr;
> +  exp.X_add_number = 0;
> +
> +  if (!resolve_expression (&exp) || exp.X_op != O_constant
> +      || exp.X_add_number < 0)
> +    as_bad (_(".seh_startepilog offset expression for %s "
> +	    "does not evaluate to a non-negative constant"),
> +	    S_GET_NAME (epilogue_start_addr));
> +
> +  if (seh_ctx_cur->epilogue_scopes_count
> +      >= seh_ctx_cur->epilogue_scopes_capacity)
> +    {
> +      const unsigned initial_capacity = 32;
> +      if (seh_ctx_cur->epilogue_scopes_capacity)
> +	seh_ctx_cur->epilogue_scopes_capacity *= 2;
> +      else
> +	seh_ctx_cur->epilogue_scopes_capacity = initial_capacity;
> +
> +      seh_ctx_cur->epilogue_scopes
> +	= XRESIZEVEC (seh_aarch64_epilogue_scope, seh_ctx_cur->epilogue_scopes,
> +		     seh_ctx_cur->epilogue_scopes_capacity);
> +    }
> +
> +  seh_aarch64_epilogue_scope *epilogue_scope = seh_ctx_cur->epilogue_scopes
> +    + seh_ctx_cur->epilogue_scopes_count;
> +  epilogue_scope->epilogue_start_offset = exp.X_add_number / 4;
> +  epilogue_scope->reserved = 0;
> +  epilogue_scope->epilogue_start_index = seh_ctx_cur->unwind_codes_byte_count;
> +  seh_ctx_cur->epilogue_scopes_count++;
> +}
> +
> +static void
> +obj_coff_seh_endepilogue (const int what ATTRIBUTE_UNUSED)
> +{
> +  if (!verify_context (".seh_endepilogue")
> +      || !seh_validate_seg (".seh_endepilogue"))
> +    return;
> +
> +  demand_empty_rest_of_line ();
> +
> +  expressionS exp;
> +  symbolS *epilogue_end_addr = symbol_temp_new_now ();
> +  exp.X_op = O_subtract;
> +  exp.X_add_symbol = epilogue_end_addr;
> +  exp.X_op_symbol = seh_ctx_cur->start_addr;
> +  exp.X_add_number = 0;
> +
> +  if (!resolve_expression (&exp) || exp.X_op != O_constant
> +      || exp.X_add_number < 0)
> +    as_bad (_(".seh_endepilogue offset expression for %s "
> +	    "does not evaluate to a non-negative constant"),
> +	    S_GET_NAME (epilogue_end_addr));
> +
> +   seh_aarch64_epilogue_scope *epilogue_scope = seh_ctx_cur->epilogue_scopes
> +     + seh_ctx_cur->epilogue_scopes_count - 1;
> +
> +   epilogue_scope->epilogue_end_offset = exp.X_add_number;
> +
> +  /* End code.  */
> +  seh_aarch64_add_unwind_element (unwind_end, 0, 0);
> +}
> +
> +/* End-of-file hook.  */
> +static void
> +free_seh_ctx (struct seh_aarch64_context *seh_ctx)
> +{
> +  free (seh_ctx->func_name);
> +  const seh_aarch64_func_fragment *fragment = seh_ctx->func_fragment.next;
> +  while (fragment)
> +    {
> +      const seh_aarch64_func_fragment *next = fragment->next;
> +      XDELETE (fragment);
> +      fragment = next;
> +    }
> +  XDELETEVEC (seh_ctx->epilogue_scopes);
> +  free (seh_ctx);
> +}
> +
> +static void
> +obj_coff_seh_save_reg (const int type)
> +{
> +  gas_assert (type >= 0 && type <= unwind_last_type);
> +
> +  const struct aarch64_unwind_code_pack_info *unwind_code_pack_info
> +    = aarch64_unwind_code_pack_data + type;
> +
> +  if (!unwind_code_pack_info->directive
> +      || !seh_validate_seg (unwind_code_pack_info->directive))
> +    return;
> +
> +  SKIP_WHITESPACE ();
> +
> +  char *symbol_name = NULL;
> +  unsigned reg = -1;
> +
> +  if (unwind_code_pack_info->reg_bits)
> +    {
> +      char name_end = get_symbol_name (&symbol_name);
> +      reg = atoi (symbol_name + 1);
> +      (void) restore_line_pointer (name_end);
> +
> +      if (!skip_whitespace_and_comma (1))
> +	return;
> +
> +      if (reg > 30)
> +	as_bad (_("register number is out of range"));
> +    }
> +
> +  offsetT off = -1;
> +  if (unwind_code_pack_info->offset_bits)
> +    {
> +      off = get_absolute_expression ();
> +
> +      if (off < 0)
> +	as_bad (_("offset is negative"));
> +    }
> +
> +  demand_empty_rest_of_line ();
> +
> +  seh_aarch64_add_unwind_element (type, off, reg);
> +}
> +
> +/* Add a stack-allocation token to current context.  */
> +static void
> +obj_coff_seh_stackalloc (const int what ATTRIBUTE_UNUSED)
> +{
> +  const offsetT off = get_absolute_expression ();
> +  demand_empty_rest_of_line ();
> +
> +  /* aarch64 offset should be encoded in multiples of sixteen.  */
> +  if ((off & 0xf) != 0)
> +    as_bad (_(".seh_stackalloc offset < 16-byte stack alignment"));
> +
> +  if (off < 0x200)
> +    seh_aarch64_add_unwind_element (unwind_alloc_s, off, 0);
> +  else if (off < 0x8000)
> +    seh_aarch64_add_unwind_element (unwind_alloc_m, off, 0);
> +  else if (off < 0x10000000)
> +    seh_aarch64_add_unwind_element (unwind_alloc_l, off, 0);
> +  else
> +    as_bad (_(".seh_stackalloc offset out of range"));
> +}
> +
> +/* Data writing routines.  */
> +static void
> +seh_aarch64_emit_epilog_scopes (const seh_context *seh_ctx,
> +				const uint64_t fragment_offset,
> +				const unsigned prolog_size,
> +				const unsigned first_fragment_scope,
> +				const unsigned last_fragment_scope,
> +				const bool has_phantom_prolog)
> +{
> +  unsigned start_index_offset = 0;
> +  const seh_aarch64_epilogue_scope *scopes = seh_ctx->epilogue_scopes;
> +  if (first_fragment_scope < seh_ctx->epilogue_scopes_count)
> +    start_index_offset = scopes[first_fragment_scope].epilogue_start_index
> +			 - prolog_size;
> +  if (has_phantom_prolog)
> +    {
> +      if (start_index_offset == 0)
> +	as_bad (_("start index offset for the epilogue cannot be 0 when "
> +		"phantom prolog is used"));
> +      --start_index_offset;
> +    }
> +
> +  for (unsigned i = first_fragment_scope; i < last_fragment_scope; ++i)
> +    {
> +      seh_aarch64_epilogue_scope scope = seh_ctx->epilogue_scopes[i];
> +      scope.epilogue_start_offset_reduced = (scope.epilogue_start_offset
> +					    - fragment_offset) >> 2;
> +      scope.epilogue_start_index -= start_index_offset;
> +      uint32_t scope_code;
> +      memcpy (&scope_code, &scope, sizeof (scope_code));
> +      md_number_to_chars (frag_more (4), scope_code, 4);
> +    }
> +}
> +
> +static void
> +seh_aarch64_emit_unwind_codes (const seh_context *seh_ctx,
> +			       const unsigned prolog_size,
> +			       const unsigned first_epilog_index,
> +			       const unsigned last_epilog_index,
> +			       const bool has_phantom_prolog)
> +{
> +  unsigned total_byte_count = 0;
> +
> +  if (has_phantom_prolog)
> +    {
> +      ++total_byte_count;
> +      md_number_to_chars (frag_more (1), AARCH64_UNOP_ENDC, 1);
> +    }
> +
> +  unsigned unwind_bytes_offset = 0;
> +  for (unsigned i = 0; i < seh_ctx->unwind_codes_count; ++i)
> +    {
> +      const seh_aarch64_unwind_code *code = seh_ctx->unwind_codes
> +					    + i;
> +      const unsigned byte_count
> +	= aarch64_unwind_code_pack_data[code->type].size;
> +      unwind_bytes_offset += byte_count;
> +
> +      if (unwind_bytes_offset > last_epilog_index)
> +	break;
> +
> +      if (unwind_bytes_offset > prolog_size
> +	  && unwind_bytes_offset <= first_epilog_index)
> +	continue;
> +
> +      /*  emit unwind code bytes in big endian.  */
> +      number_to_chars_bigendian (frag_more (byte_count), code->value,
> +				 byte_count);
> +      total_byte_count += byte_count;
> +    }
> +
> +    /* handle word alignment.  */
> +    unsigned required_padding = (4 - total_byte_count % 4) % 4;
> +    if (required_padding)
> +      {
> +	/* Use AARCH64_UNOP_NOP for alignment.  */
> +	const uint32_t nop_chain = (AARCH64_UNOP_NOP << 24)
> +				   | (AARCH64_UNOP_NOP << 16)
> +				   | (AARCH64_UNOP_NOP << 8)
> +				   | AARCH64_UNOP_NOP;
> +
> +	md_number_to_chars (frag_more (required_padding), nop_chain,
> +			    required_padding);
> +      }
> +}
> +
> +static bool
> +seh_function_size (const struct seh_aarch64_context *seh_ctx,
> +		  uintptr_t *size)
> +{
> +  fragS *start_frag, *end_frag;
> +  addressT start_offset, end_offset;
> +  start_frag = symbol_get_frag_and_value (seh_ctx->start_addr, &start_offset);
> +  end_frag = symbol_get_frag_and_value (seh_ctx->end_addr, &end_offset);
> +
> +  intptr_t func_size = end_frag->fr_address + end_offset
> +		       - start_frag->fr_address - start_offset;
> +  if (func_size < 0)
> +    return false;
> +
> +  *size = func_size;
> +  return true;
> +}
> +
> +/* Write out the xdata information for one function.  */
> +static void
> +seh_aarch64_write_function_xdata (struct seh_aarch64_context *seh_ctx)
> +{

Is there a way to break down this function? Perhaps around your 
conditionals inside the while (true) loop?

> +  if (!seh_ctx->unwind_codes_byte_count)
> +    return;
> +
> +  const segT save_seg = now_seg;
> +  const subsegT save_subseg = now_subseg;
> +
> +  switch_xdata (seh_ctx->subsection, seh_ctx->code_seg);
> +
> +  /* Set 4-byte alignment.  */
> +  frag_align (2, 0, 0);
> +
> +  uintptr_t func_size = 0;
> +  if (!seh_function_size (seh_ctx, &func_size))
> +    {
> +      as_bad (_("the function size for %s has not been evaluated"),
> +	      seh_ctx->func_name);
> +      return;
> +    }
> +
> +  /* The large functions should be split into fragments smaller than 1MB with
> +     4 bytes alignment.
> +     https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170#large-functions.  */
> +  const unsigned max_frag_size = (1 << 20) - 4;
> +  const bool is_fragmented_function = func_size > max_frag_size;
> +
> +  /* [first_fragment_scope, last_fragment_scope).  */
> +  unsigned prolog_insn_count = 0;
> +  for (unsigned i = 0; i < seh_ctx->unwind_codes_count; ++i)
> +    {
> +      if (seh_ctx->unwind_codes[i].type == unwind_end)
> +	{
> +	  prolog_insn_count = i + 1;
> +	  break;
> +	}
> +    }
> +
> +  unsigned prolog_size;
> +  if (seh_ctx->epilogue_scopes_count)
> +    prolog_size = seh_ctx->epilogue_scopes[0].epilogue_start_index;
> +  else
> +    prolog_size = seh_ctx->unwind_codes_byte_count;
> +
> +  seh_aarch64_func_fragment *fragment;
> +  fragment = &seh_ctx->func_fragment;
> +  uintptr_t fragment_offset = 0;
> +  unsigned first_fragment_scope = 0;
> +  unsigned last_fragment_scope = 0;
> +  while (true)
> +    {
> +      fragment->xdata_addr = symbol_temp_new_now ();
> +      fragment->offset = fragment_offset;
> +      fragment->next = NULL;
> +
> +      uintptr_t frag_size = func_size - fragment_offset;
> +      if (frag_size > max_frag_size)
> +	frag_size = max_frag_size;
> +
> +      const bool is_first_frag = fragment_offset == 0;
> +      const bool is_last_frag = (fragment_offset + frag_size) == func_size;
> +
> +      if (!is_fragmented_function)
> +	last_fragment_scope = seh_ctx->epilogue_scopes_count;
> +      else
> +	{
> +	  first_fragment_scope = last_fragment_scope;
> +	  for (unsigned i = first_fragment_scope;
> +	       i < seh_ctx->epilogue_scopes_count; ++i)
> +	    {
> +	      const seh_aarch64_epilogue_scope *scope
> +		= seh_ctx->epilogue_scopes;
> +	      scope += i;
> +	      if (scope->epilogue_start_offset >= (fragment_offset + frag_size))
> +		break;
> +
> +	      if (scope->epilogue_end_offset >= (fragment_offset + frag_size))
> +		{
> +		  frag_size = scope->epilogue_start_offset - fragment_offset;
> +		  break;
> +		}
> +
> +	      if (scope->epilogue_start_offset >= fragment_offset)
> +		last_fragment_scope = i + 1;
> +	    }
> +	}
> +
> +      seh_aarch64_xdata_header *header = &seh_ctx->xdata_header;
> +      const seh_aarch64_epilogue_scope *scopes = seh_ctx->epilogue_scopes;
> +
> +      const uint32_t func_length_encoded = frag_size >> 2;
> +      header->func_length = func_length_encoded;
> +      header->vers = 0;
> +      header->e = 0;
> +      header->code_words = 0;
> +      header->epilogue_count = 0;
> +
> +      header->ext_code_words = 0;
> +      header->ext_epilogue_count = last_fragment_scope
> +				   - first_fragment_scope;
> +      header->reserved = 0;
> +
> +      unsigned first_epilog_index = 0;
> +      unsigned last_epilog_index = 0;
> +      if (!header->ext_epilogue_count)
> +	{
> +	  first_epilog_index = prolog_size;
> +	  last_epilog_index = prolog_size;
> +	}
> +      else
> +	{
> +	  const seh_aarch64_epilogue_scope *scope;
> +	  scope = scopes + first_fragment_scope;
> +	  first_epilog_index = scope->epilogue_start_index;
> +	  if (last_fragment_scope == seh_ctx->epilogue_scopes_count)
> +	    last_epilog_index = seh_ctx->unwind_codes_byte_count;
> +	  else
> +	    {
> +	      scope = scopes + last_fragment_scope;
> +	      last_epilog_index = scope->epilogue_start_index;
> +	    }
> +	}
> +
> +      unsigned unwind_bytes = 0;
> +      if (is_first_frag || is_last_frag)
> +	unwind_bytes += prolog_size;
> +
> +      if (header->ext_epilogue_count)
> +	unwind_bytes += last_epilog_index - first_epilog_index;
> +
> +      const bool has_phantom_prolog = is_fragmented_function && is_last_frag;
> +      if (has_phantom_prolog && unwind_bytes)
> +	{
> +	  /* One more epilogue scope and unwind code are emitted with phantom
> +	     prolog.  */
> +	  unwind_bytes += 1;
> +	  ++header->ext_epilogue_count;
> +	}
> +
> +      /* Calculate the number of code words with 4-byte alignment.  */
> +      header->ext_code_words = (unwind_bytes + 3) / 4;
> +
> +      if ((header->ext_code_words == 0 && header->ext_epilogue_count == 0)
> +	  || header->ext_code_words > 31
> +	  || header->ext_epilogue_count > 31)
> +	md_number_to_chars (frag_more (8),
> +			   seh_ctx->xdata_header_value, 8);
> +      else
> +	{
> +	  header->code_words = header->ext_code_words;
> +	  header->epilogue_count = header->ext_epilogue_count;
> +	  if (header->epilogue_count == 1)
> +	    {
> +	      header->e = 1;
> +	      if (has_phantom_prolog)
> +		header->ext_epilogue_count = 0;
> +	      else
> +		{
> +		  const seh_aarch64_epilogue_scope *scope;
> +		  scope = scopes + first_fragment_scope;
> +		  header->ext_epilogue_count = scope->epilogue_start_index;
> +		}
> +	    }
> +	  md_number_to_chars (frag_more (4),
> +			      seh_ctx->xdata_header_value, 4);
> +	}
> +
> +      if (header->ext_epilogue_count && !header->e)
> +	{
> +	  seh_aarch64_emit_epilog_scopes (seh_ctx,
> +					 fragment_offset, prolog_size,
> +					 first_fragment_scope,
> +					 last_fragment_scope,
> +					 has_phantom_prolog);
> +	  if (has_phantom_prolog)
> +	    {
> +	      const uint32_t epilog_start_index_encoded = 1 << 22;
> +	      const uint32_t epilog_start_offset_encoded
> +		= (frag_size - prolog_insn_count * 4) >> 2;
> +	      md_number_to_chars (frag_more (4),
> +				  epilog_start_index_encoded
> +				  | epilog_start_offset_encoded, 4);
> +	    }
> +	}
> +
> +      if (header->ext_code_words)
> +	seh_aarch64_emit_unwind_codes (seh_ctx, prolog_size, first_epilog_index,
> +				       last_epilog_index, has_phantom_prolog);
> +
> +      if (header->x == 1)
> +	{
> +	  if (seh_ctx->handler.X_op == O_symbol)
> +	    seh_ctx->handler.X_op = O_symbol_rva;
> +
> +	  emit_expr (&seh_ctx->handler, 4);
> +	}
> +
> +      fragment_offset += frag_size;
> +      if (fragment_offset == func_size)
> +	break;
> +
> +      fragment->next = XCNEW (seh_aarch64_func_fragment);
> +      fragment = fragment->next;
> +    }
> +
> +  subseg_set (save_seg, save_subseg);
> +}
> +
> +/* Write out pdata for one function.  */
> +static void
> +seh_aarch64_write_function_pdata (const seh_context *seh_ctx)
> +{
> +  expressionS exp;
> +  const segT save_seg = now_seg;
> +  const subsegT save_subseg = now_subseg;
> +  memset (&exp, 0, sizeof (expressionS));
> +  switch_pdata (seh_ctx->code_seg);
> +
> +  if (seh_ctx->unwind_codes_byte_count)
> +    {
> +      const seh_aarch64_func_fragment *fragment = &seh_ctx->func_fragment;
> +      while (fragment)
> +	{
> +	  exp.X_op = O_symbol_rva;
> +	  exp.X_add_number = fragment->offset;
> +	  exp.X_add_symbol = seh_ctx->start_addr;
> +	  emit_expr (&exp, 4);
> +
> +	  exp.X_op = O_symbol_rva;
> +	  /* TODO: Implementing packed unwind data.  */
> +	  exp.X_add_number = 0;
> +	  exp.X_add_symbol = fragment->xdata_addr;
> +	  emit_expr (&exp, 4);
> +	  fragment = fragment->next;
> +	}
> +    }
> +
> +  subseg_set (save_seg, save_subseg);
> +}
> +
> +void
> +seh_aarch64_write_data (void)
> +{
> +  if (in_seh_proc)
> +    as_bad (_("open SEH entry at end of file (missing .seh_endproc)"));
> +
> +  if (!seh_ctx_root)
> +    return;
> +
> +  struct seh_aarch64_context *seh_ctx = seh_ctx_root;
> +  seh_ctx_root = NULL;
> +
> +  /* Relax the segment to be able to calculate the function sizes.  */
> +  subsegs_finish_section (seh_ctx->code_seg);
> +  const segment_info_type *seginfo = seg_info (seh_ctx->code_seg);
> +  relax_segment (seginfo->frchainP->frch_root, seh_ctx->code_seg, 0);
> +
> +  while (seh_ctx)
> +  {
> +    seh_aarch64_write_function_xdata (seh_ctx);
> +    seh_aarch64_write_function_pdata (seh_ctx);
> +    struct seh_aarch64_context *next = seh_ctx->next;
> +    free_seh_ctx (seh_ctx);
> +    seh_ctx = next;
> +  }
> +}
> +
> +void
> +obj_coff_seh_do_final (void)
> +{
> +}
> diff --git a/gas/config/obj-coff-seh-aarch64.h b/gas/config/obj-coff-seh-aarch64.h
> new file mode 100644
> index 00000000000..bda7e00adc5
> --- /dev/null
> +++ b/gas/config/obj-coff-seh-aarch64.h
> @@ -0,0 +1,265 @@
> +/* SEH .pdata/.xdata COFF object file format on AArch64
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +
> +   This file is part of GAS.
> +
> +   GAS is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3, or (at your option)
> +   any later version.
> +
> +   GAS is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with GAS; see the file COPYING.  If not, write to the Free
> +   Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
> +   02110-1301, USA.  */
> +
> +/* SEH COFF AArch64 implementation partially intersects with the x64
> +   version, however it has a different extension to the unwind codes.
> +   It emits SEH data to pdata and xdata sections.  In some cases SEH
> +   data could be emitted to a packed record in the pdata section
> +   without the need for data in the xdata section.  However, the packed
> +   pdata record is not implemented yet.  */
> +
> +#ifndef OBJ_COFF_SEH_AARCH64_H
> +#define OBJ_COFF_SEH_AARCH64_H
> +
> +typedef enum seh_aarch64_unwind_types
> +{

Can you add a link to the documentation of the unwind types?


Thanks,

Saurabh

> +  unwind_alloc_s,
> +  unwind_alloc_m,
> +  unwind_alloc_l,
> +  unwind_save_reg,
> +  unwind_save_reg_x,
> +  unwind_save_regp,
> +  unwind_save_regp_x,
> +  unwind_save_fregp,
> +  unwind_save_fregp_x,
> +  unwind_save_freg,
> +  unwind_save_freg_x,
> +  unwind_save_lrpair,
> +  unwind_save_fplr,
> +  unwind_save_fplr_x,
> +  unwind_save_r19r20_x,
> +  unwind_add_fp,
> +  unwind_set_fp,
> +  unwind_save_next,
> +  unwind_nop,
> +  unwind_pac_sign_lr,
> +  unwind_end,
> +  unwind_end_c,
> +  unwind_last_type = unwind_end_c
> +} seh_aarch64_unwind_types;
> +
> +#define SEH_CMDS						      \
> +  /* Start a function that contains SEH.  */			      \
> +  {"seh_proc", obj_coff_seh_proc, 0},				      \
> +								      \
> +  /* End a function that contains SEH.  */			      \
> +  {"seh_endproc", obj_coff_seh_endproc, 0},			      \
> +								      \
> +  /* End a SEH prolog with unwinding codes.  */			      \
> +  {"seh_endprologue", obj_coff_seh_endprologue, 0},		      \
> +								      \
> +  /* Allocate stack.  */					      \
> +  {"seh_stackalloc", obj_coff_seh_stackalloc, 0},		      \
> +								      \
> +  /* Set a SEH handler.  */					      \
> +  {"seh_handler", obj_coff_seh_handler, 0},			      \
> +								      \
> +  /* Set a SEH handler data.  */				      \
> +  {"seh_handlerdata", obj_coff_seh_handlerdata, 0},		      \
> +								      \
> +  /* Start a SEH epilogue.  */					      \
> +  {"seh_startepilogue", obj_coff_seh_startepilogue, 0},		      \
> +								      \
> +  /* End a SEH epilogue.  */					      \
> +  {"seh_endepilogue", obj_coff_seh_endepilogue, 0},		      \
> +								      \
> +  /* Save an 'x' register.  */					      \
> +  {"seh_save_reg", obj_coff_seh_save_reg, unwind_save_reg},	      \
> +								      \
> +  /* Save an 'x' register with a pre-indexed offset.  */	      \
> +  {"seh_save_reg_x", obj_coff_seh_save_reg, unwind_save_reg_x},	      \
> +								      \
> +  /* Save an 'x' register pair.  */				      \
> +  {"seh_save_regp", obj_coff_seh_save_reg, unwind_save_regp},	      \
> +								      \
> +  /* Save an 'x' register pair with a pre-indexed offset.  */	      \
> +  {"seh_save_regp_x", obj_coff_seh_save_reg, unwind_save_regp_x},     \
> +								      \
> +  /* Save an 'x' register and lr.  */				      \
> +  {"seh_save_lrpair", obj_coff_seh_save_reg, unwind_save_lrpair},     \
> +								      \
> +  /* Save a 'd' register pair.  */				      \
> +  {"seh_save_fregp", obj_coff_seh_save_reg, unwind_save_fregp},	      \
> +								      \
> +  /* Save a 'd' register pair with a pre-indexed offset.  */	      \
> +  {"seh_save_fregp_x", obj_coff_seh_save_reg, unwind_save_fregp_x},   \
> +								      \
> +  /* Save a 'd' register.  */					      \
> +  {"seh_save_freg", obj_coff_seh_save_reg, unwind_save_freg},	      \
> +								      \
> +  /* Save a 'd' register with a pre-indexed offset.  */		      \
> +  {"seh_save_freg_x", obj_coff_seh_save_reg, unwind_save_freg_x},     \
> +								      \
> +  /* Save fp and lr registers.  */				      \
> +  {"seh_save_fplr", obj_coff_seh_save_reg, unwind_save_fplr},	      \
> +								      \
> +  /* Save fp and lr registers with a pre-indexed offset.  */	      \
> +  {"seh_save_fplr_x", obj_coff_seh_save_reg, unwind_save_fplr_x},     \
> +								      \
> +  /* Save x19 and x20 registers with a pre-indexed offset.  */	      \
> +  {"seh_save_r19r20_x", obj_coff_seh_save_reg, unwind_save_r19r20_x}, \
> +								      \
> +  /* Set fp by sp + offset.  */					      \
> +  {"seh_add_fp", obj_coff_seh_save_reg, unwind_add_fp},		      \
> +								      \
> +  /* Unwind operation is not required.  */			      \
> +  {"seh_nop", obj_coff_seh_save_reg, unwind_nop},		      \
> +								      \
> +  /* Sign the return address in lr with pacibsp.  */		      \
> +  {"seh_pac_sign_lr", obj_coff_seh_save_reg, unwind_pac_sign_lr},     \
> +								      \
> +  /* Set fp by sp.  */						      \
> +  {"seh_set_fp", obj_coff_seh_save_reg, unwind_set_fp},		      \
> +								      \
> +  /* Save next register pair.  */				      \
> +  {"seh_save_next", obj_coff_seh_save_reg, unwind_save_next},
> +
> +/* AArch64 exceptions handling and unwinding structures.
> +   https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling#pdata-records.  */
> +
> +typedef struct seh_aarch64_unwind_code
> +{
> +  unsigned value;
> +  seh_aarch64_unwind_types type;
> +} seh_aarch64_unwind_code;
> +
> +typedef struct seh_aarch64_packed_unwind_data
> +{
> +  uint32_t flag : 2;
> +  uint32_t func_length : 11;
> +  uint32_t frame_size : 9;
> +  uint32_t cr : 2;
> +  uint32_t h : 1;
> +  uint32_t regI : 4;
> +  uint32_t regF : 3;
> +} seh_aarch64_packed_unwind_data;
> +
> +typedef struct seh_aarch64_except_info
> +{
> +  uint32_t flag : 2;
> +  uint32_t except_info_rva : 30;
> +} seh_aarch64_except_info;
> +
> +typedef union seh_aarch64_unwind_info
> +{
> +  seh_aarch64_except_info except_info;
> +  seh_aarch64_packed_unwind_data packed_unwind_data;
> +} seh_aarch64_unwind_info;
> +
> +typedef struct seh_aarch64_xdata_header
> +{
> +  uint32_t func_length : 18;
> +  uint32_t vers : 2;
> +  uint32_t x : 1;
> +  uint32_t e : 1;
> +  uint32_t epilogue_count : 5;
> +  uint32_t code_words : 5;
> +  uint32_t ext_epilogue_count : 16;
> +  uint32_t ext_code_words : 8;
> +  uint32_t reserved : 8;
> +} seh_aarch64_xdata_header;
> +
> +typedef struct seh_aarch64_epilogue_scope
> +{
> +  uint32_t epilogue_start_offset_reduced : 18;
> +  uint32_t reserved : 4;
> +  uint32_t epilogue_start_index : 10;
> +  bfd_vma epilogue_start_offset;
> +  bfd_vma epilogue_end_offset;
> +} seh_aarch64_epilogue_scope;
> +
> +typedef struct seh_aarch64_func_fragment
> +{
> +  bfd_vma offset;
> +  symbolS *xdata_addr;
> +  struct seh_aarch64_func_fragment *next;
> +} seh_aarch64_func_fragment;
> +
> +/* AARCH64_MAX_UNWIND_CODES is limited by
> +   seh_aarch64_xdata_header::ext_code_words.  */
> +#define AARCH64_MAX_UNWIND_CODES (255 * 4)
> +#define AARCH64_MAX_UNWIND_CODES_SIZE (255 * 4)
> +/* AARCH64_MAX_EPILOGUE_SCOPES is limited by
> +   seh_aarch64_xdata_header::ext_epilogue_count.  */
> +#define AARCH64_MAX_EPILOGUE_SCOPES 65535
> +
> +typedef struct seh_aarch64_context
> +{
> +  struct seh_aarch64_context *next;
> +
> +  /* Initial code-segment.  */
> +  segT code_seg;
> +  /* Function name.  */
> +  char *func_name;
> +  /* BeginAddress.  */
> +  symbolS *start_addr;
> +  /* EndAddress.  */
> +  symbolS *end_addr;
> +  /* PrologueEnd.  */
> +  symbolS *endprologue_addr;
> +  /* ExceptionHandler.  */
> +  expressionS handler;
> +  /* ExceptionHandlerData.  */
> +  expressionS handler_data;
> +
> +  subsegT subsection;
> +
> +  union {
> +    seh_aarch64_xdata_header xdata_header;
> +    valueT xdata_header_value;
> +  };
> +  unsigned unwind_codes_count;
> +  unsigned unwind_codes_byte_count;
> +  seh_aarch64_unwind_code unwind_codes[AARCH64_MAX_UNWIND_CODES];
> +  unsigned epilogue_scopes_count;
> +  unsigned epilogue_scopes_capacity;
> +  seh_aarch64_epilogue_scope *epilogue_scopes;
> +  expressionS except_handler;
> +  expressionS except_handler_data;
> +  /* The function fragments.  */
> +  seh_aarch64_func_fragment func_fragment;
> +} seh_context;
> +
> +/* aarch64 unwind code prefixes.  */
> +
> +#define AARCH64_UNOP_ALLOCS	  0b000U
> +#define AARCH64_UNOP_SAVER19R20X  0b001U
> +#define AARCH64_UNOP_SAVEFPLR	  0b01U
> +#define AARCH64_UNOP_SAVEFPLRX	  0b10U
> +#define AARCH64_UNOP_ALLOCM	  0b11000U
> +#define AARCH64_UNOP_SAVEREGP	  0b110010U
> +#define AARCH64_UNOP_SAVEREGPX	  0b110011U
> +#define AARCH64_UNOP_SAVEREG	  0b110100U
> +#define AARCH64_UNOP_SAVEREGX	  0b1101010U
> +#define AARCH64_UNOP_SAVELRPAIR	  0b1101011U
> +#define AARCH64_UNOP_SAVEFREGP	  0b1101100U
> +#define AARCH64_UNOP_SAVEFREGPX	  0b1101101U
> +#define AARCH64_UNOP_SAVEFREG	  0b1101110U
> +#define AARCH64_UNOP_SAVEFREGX	  0b11011110U
> +#define AARCH64_UNOP_ALLOCL	  0b11100000U
> +#define AARCH64_UNOP_SETFP	  0b11100001U
> +#define AARCH64_UNOP_ADDFP	  0b11100010U
> +#define AARCH64_UNOP_NOP	  0b11100011U
> +#define AARCH64_UNOP_END	  0b11100100U
> +#define AARCH64_UNOP_ENDC	  0b11100101U
> +#define AARCH64_UNOP_SAVENEXT	  0b11100110U
> +#define AARCH64_UNOP_PACSIGNLR	  0b11111100U
> +
> +#endif /* OBJ_COFF_SEH_AARCH64_H.  */
> diff --git a/gas/config/obj-coff-seh-shared.c b/gas/config/obj-coff-seh-shared.c
> index f3afd981aca..132470c70e6 100644
> --- a/gas/config/obj-coff-seh-shared.c
> +++ b/gas/config/obj-coff-seh-shared.c
> @@ -19,8 +19,13 @@
>      Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
>      02110-1301, USA.  */
>   
> +#if defined (COFFAARCH64)
> +#include "obj-coff-seh-aarch64.h"
> +typedef struct seh_aarch64_context seh_context_t;
> +#else
>   #include "obj-coff-seh.h"
>   typedef struct seh_context seh_context_t;
> +#endif
>   
>   /* Private segment collection list.  */
>   struct seh_seg_list {
> diff --git a/gas/config/obj-coff.c b/gas/config/obj-coff.c
> index 7732c0af911..b08295019d8 100644
> --- a/gas/config/obj-coff.c
> +++ b/gas/config/obj-coff.c
> @@ -55,7 +55,11 @@ static const char weak_altprefix[] = ".weak.";
>   #endif /* TE_PE */
>   
>   #include "obj-coff-seh-shared.c"
> +#if defined (COFFAARCH64)
> +#include "obj-coff-seh-aarch64.c"
> +#else
>   #include "obj-coff-seh.c"
> +#endif
>   
>   typedef struct
>     {
> diff --git a/gas/config/tc-aarch64.c b/gas/config/tc-aarch64.c
> index cd76163488c..d9d3296a458 100644
> --- a/gas/config/tc-aarch64.c
> +++ b/gas/config/tc-aarch64.c
> @@ -10375,6 +10375,16 @@ aarch64_cleanup (void)
>       }
>   }
>   
> +#if defined (OBJ_COFF)
> +/* Called after all assembly has been done.  */
> +
> +void
> +aarch64_md_finish (void)
> +{
> +  seh_aarch64_write_data();
> +}
> +#endif /* OBJ_COFF.  */
> +
>   #ifdef OBJ_ELF
>   /* Remove any excess mapping symbols generated for alignment frags in
>      SEC.  We may have created a mapping symbol before a zero byte
> diff --git a/gas/config/tc-aarch64.h b/gas/config/tc-aarch64.h
> index d1fb4c9058b..67f476a3dcc 100644
> --- a/gas/config/tc-aarch64.h
> +++ b/gas/config/tc-aarch64.h
> @@ -82,6 +82,11 @@ struct aarch64_fix
>   
>   #define tc_frob_section(S) aarch64_frob_section (S)
>   
> +#if defined (OBJ_COFF)
> +#define md_finish aarch64_md_finish
> +extern void aarch64_md_finish (void);
> +#endif
> +
>   /* The key used to sign a function's return address.  */
>   enum pointer_auth_key {
>     AARCH64_PAUTH_KEY_A,
> @@ -363,6 +368,7 @@ extern void aarch64_handle_align (struct frag *);
>   extern int tc_aarch64_regname_to_dw2regnum (char *regname);
>   extern void tc_aarch64_frame_initial_instructions (void);
>   extern bool aarch64_fix_adjustable (struct fix *);
> +extern void seh_aarch64_write_data (void);
>   
>   #ifdef TE_PE
>   
> diff --git a/gas/write.c b/gas/write.c
> index 9d0777051dd..903f858b4dc 100644
> --- a/gas/write.c
> +++ b/gas/write.c
> @@ -1831,7 +1831,7 @@ set_symtab (void)
>   #endif
>   #endif
>   
> -static void
> +void
>   subsegs_finish_section (asection *s)
>   {
>     struct frchain *frchainP;
> diff --git a/gas/write.h b/gas/write.h
> index 7281930c2d6..0638e5958d6 100644
> --- a/gas/write.h
> +++ b/gas/write.h
> @@ -188,5 +188,6 @@ extern fixS *fix_new_exp (fragS *, unsigned long, unsigned long,
>   			  const expressionS *, int, bfd_reloc_code_real_type);
>   extern void write_print_statistics (FILE *);
>   extern void as_bad_subtract (fixS *);
> +extern void subsegs_finish_section (asection *);
>   
>   #endif /* __write_h__ */
  
Alice Carlotti April 16, 2026, 2:38 a.m. UTC | #2
On Thu, Mar 19, 2026 at 12:16:38PM +0100, Evgeny Karpov wrote:

Apologies for the long wait - it's taken me a while to get to grips with the
spec and implementation in detail.

> The patch reuses shared helpers for SEH and implements SEH on AArch64.
> The implementation is based on
> (https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170)
> and pdata/xdata SEH records are emitted from md_finish.
> 
> When .pdata/.xdata is emitted, the function size is required.
> Function sizes are calculated as late as possible, and the code segment needs
> to be relaxed to be able to calculate the function sizes.
> 
> Initially, obj_coff_generate_pdata was declared in write_object_file.

I think you mean "called in write_object_file".

> Before the change, obj_coff_generate_pdata was used only to validate
> syntax, which was sufficient for that purpose. However, that location
> seems incorrect, as it is too late to emit .pdata/.xdata records
> in the AArch64 case.
> 
> md_finish has been declared for AArch64 and extended with
> seh_aarch64_write_data to emit .pdata/.xdata records after all
> assembly has been completed.
> 
> Signed-off-by: Evgeny Karpov <evgeny@kmaps.co>

Some general remarks:

- Epilogue/epilog and prologue/prolog in various names.  We should be
  consistent with Microsoft's description, so remove the "ue"s.

- It hard to know whether the input parsing is correct when this patch doesn't
  include any tests or documentation for the assembler directives.

> 
> gas/ChangeLog:
> 	* gas/config/obj-coff-seh-shared.c (defined): Update.
> 	* gas/config/obj-coff.c (defined): Update.
> 	* gas/config/tc-aarch64.c (defined): Add OBJ_COFF guard.
> 	(aarch64_md_finish): Add.
> 	* gas/config/tc-aarch64.h (defined): Add OBJ_COFF guard.
> 	(md_finish): Add.
> 	(aarch64_md_finish): Add.
> 	(seh_aarch64_write_data): Add.
> 	* gas/write.c: Update.
> 	* gas/write.h (subsegs_finish_section): Update.
> 	* gas/config/obj-coff-seh-aarch64.c: New file.
> 	* gas/config/obj-coff-seh-aarch64.h: New file.
> ---
>  gas/config/obj-coff-seh-aarch64.c | 889 ++++++++++++++++++++++++++++++
>  gas/config/obj-coff-seh-aarch64.h | 265 +++++++++
>  gas/config/obj-coff-seh-shared.c  |   5 +
>  gas/config/obj-coff.c             |   4 +
>  gas/config/tc-aarch64.c           |  10 +
>  gas/config/tc-aarch64.h           |   6 +
>  gas/write.c                       |   2 +-
>  gas/write.h                       |   1 +
>  8 files changed, 1181 insertions(+), 1 deletion(-)
>  create mode 100644 gas/config/obj-coff-seh-aarch64.c
>  create mode 100644 gas/config/obj-coff-seh-aarch64.h
> 
> diff --git a/gas/config/obj-coff-seh-aarch64.c b/gas/config/obj-coff-seh-aarch64.c
> new file mode 100644
> index 00000000000..6b5354f6544
> --- /dev/null
> +++ b/gas/config/obj-coff-seh-aarch64.c
> @@ -0,0 +1,889 @@
> +/* SEH .pdata/.xdata COFF object file format on AArch64
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +
> +   This file is part of GAS.
> +
> +   GAS is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3, or (at your option)
> +   any later version.
> +
> +   GAS is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with GAS; see the file COPYING.  If not, write to the Free
> +   Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
> +   02110-1301, USA.  */
> +
> +#include "obj-coff-seh-aarch64.h"
> +
> +static struct seh_aarch64_context *seh_ctx_root = NULL;
> +static bool in_seh_proc = false;
> +
> +struct aarch64_unwind_code_pack_info {
> +  const char *directive;
> +  unsigned offset_bits;
> +  unsigned reg_bits;
> +  unsigned code_bits;
> +  unsigned code;
> +  unsigned offset_right_shift;
> +  unsigned offset;
> +  unsigned reg_right_shift;
> +  unsigned reg_offset;
> +  unsigned size;

The non-pointer members can all use 'unsigned char'.

The word "offset" has two different meanings here - how about using
offset_addend and reg_addend instead?

Specifying the shift direction in the names is unnecessary.

Also see below about field ordering.

> +};
> +
> +static const struct aarch64_unwind_code_pack_info
> +aarch64_unwind_code_pack_data[] = {
> +/* Unwind codes packing for AArch64 is described at

Nit: s/is/are/, and I think this comment belongs above the array type rather
than inside the braces.

These codes are for the non-packed unwind format, so 'pack' should be dropped
from the names and description.  You could also add that the array is indexed
by the seh_aarch64_unwind_types enum.

> +   https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170#unwind-codes
> +   and calculated in seh_aarch64_add_unwind_element function.  */
> +  {
> +    .directive = NULL, .offset_bits = 5, .reg_bits = 0,
> +    .code_bits = 3, .code = AARCH64_UNOP_ALLOCS, .offset_right_shift = 4,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },

I find this layout rather difficult to read.  Reordering the field definitions
and adding more line breaks would help - for example:

+  {
+    .directive = NULL,
+    .size = 1,
+    .code_bits = 3, .code = AARCH64_UNOP_ALLOCS,
+    .offset_bits = 5, .offset_shift = 4, .offset_addend = 0,
+    .reg_bits = 0, .reg_shift = 0, .reg_addend = 0
+  },

The AARCH64_UNOP_* defines add a level of indirection without (in my eyes)
improving clarity, so I'd drop those (but add in a comment with the unwind code
name when this can't be deduced from the directive).

Members that are NULL or 0 can be empty initialised.  If you also eliminate the
code_bits member (reducing the struct size to 16 bytes), then this reduces the
above example to:

+  {
+    /* alloc_s */
+    .size = 1, .code = 0b000,
+    .offset_bits = 5, .offset_right_shift = 4,
+  },


> +  {
> +    .directive = NULL, .offset_bits = 11, .reg_bits = 0,
> +    .code_bits = 5, .code = AARCH64_UNOP_ALLOCM, .offset_right_shift = 4,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 2
> +  },
> +  {
> +    .directive = NULL, .offset_bits = 24, .reg_bits = 0,
> +    .code_bits = 8, .code = AARCH64_UNOP_ALLOCL, .offset_right_shift = 4,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 4
> +  },
> +  {
> +    .directive = ".seh_save_reg", .offset_bits = 6, .reg_bits = 4,
> +    .code_bits = 6, .code = AARCH64_UNOP_SAVEREG, .offset_right_shift = 3,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 19, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_reg_x", .offset_bits = 5, .reg_bits = 4,
> +    .code_bits = 7, .code = AARCH64_UNOP_SAVEREGX, .offset_right_shift = 3,
> +    .offset = 1, .reg_right_shift = 0, .reg_offset = 19, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_regp", .offset_bits = 6, .reg_bits = 4,
> +    .code_bits = 6, .code = AARCH64_UNOP_SAVEREGP, .offset_right_shift = 3,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 19, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_regp_x", .offset_bits = 6, .reg_bits = 4,
> +    .code_bits = 6, .code = AARCH64_UNOP_SAVEREGPX, .offset_right_shift = 3,
> +    .offset = 1, .reg_right_shift = 0, .reg_offset = 19, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_fregp", .offset_bits = 6, .reg_bits = 3,
> +    .code_bits = 7, .code = AARCH64_UNOP_SAVEFREGP, .offset_right_shift = 3,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 8, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_fregp_x", .offset_bits = 6, .reg_bits = 3,
> +    .code_bits = 7, .code = AARCH64_UNOP_SAVEFREGPX, .offset_right_shift = 3,
> +    .offset = 1, .reg_right_shift = 0, .reg_offset = 8, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_freg", .offset_bits = 6, .reg_bits = 3,
> +    .code_bits = 7, .code = AARCH64_UNOP_SAVEFREG, .offset_right_shift = 3,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 8, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_freg_x", .offset_bits = 5, .reg_bits = 3,
> +    .code_bits = 8, .code = AARCH64_UNOP_SAVEFREGX, .offset_right_shift = 3,
> +    .offset = 1, .reg_right_shift = 0, .reg_offset = 8, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_lrpair", .offset_bits = 6, .reg_bits = 3,
> +    .code_bits = 7, .code = AARCH64_UNOP_SAVELRPAIR, .offset_right_shift = 3,
> +    .offset = 0, .reg_right_shift = 1, .reg_offset = 19, .size = 2
> +  },
> +  {
> +    .directive = ".seh_save_fplr", .offset_bits = 6, .reg_bits = 0,
> +    .code_bits = 2, .code = AARCH64_UNOP_SAVEFPLR, .offset_right_shift = 3,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },
> +  {
> +    .directive = ".seh_save_fplr_x", .offset_bits = 6, .reg_bits = 0,
> +    .code_bits = 2, .code = AARCH64_UNOP_SAVEFPLRX, .offset_right_shift = 3,
> +    .offset = 1, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },
> +  {
> +    .directive = ".seh_save_r19r20_x", .offset_bits = 5, .reg_bits = 0,
> +    .code_bits = 3, .code = AARCH64_UNOP_SAVER19R20X, .offset_right_shift = 3,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },
> +  {
> +    .directive = ".seh_add_fp",	.offset_bits = 8, .reg_bits = 0,
> +    .code_bits = 8, .code = AARCH64_UNOP_ADDFP,	.offset_right_shift = 0,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 2
> +  },
> +  {
> +    .directive = ".seh_set_fp",	.offset_bits = 0, .reg_bits = 0,
> +    .code_bits = 8, .code = AARCH64_UNOP_SETFP,	.offset_right_shift = 0,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },
> +  {
> +    .directive = ".seh_save_next", .offset_bits = 0, .reg_bits = 0,
> +    .code_bits = 8, .code = AARCH64_UNOP_SAVENEXT, .offset_right_shift = 0,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },
> +  {
> +    .directive = ".seh_nop", .offset_bits = 0, .reg_bits = 0,
> +    .code_bits = 8, .code = AARCH64_UNOP_NOP, .offset_right_shift = 0,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },
> +  {
> +    .directive = ".seh_pac_sign_lr", .offset_bits = 0, .reg_bits = 0,
> +    .code_bits = 8, .code = AARCH64_UNOP_PACSIGNLR, .offset_right_shift = 0,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },
> +  {
> +    .directive = NULL, .offset_bits = 0, .reg_bits = 0,
> +    .code_bits = 8, .code = AARCH64_UNOP_END, .offset_right_shift = 0,
> +    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
> +  },
> +};
> +
> +/* Set for current context the default handler.  */
> +static void
> +obj_coff_seh_handler (const int what ATTRIBUTE_UNUSED)
> +{
> +  char *symbol_name;
> +  char name_end;
> +
> +  if (!verify_context (".seh_handler"))
> +    return;
> +
> +  if (*input_line_pointer == 0 || *input_line_pointer == '\n')
> +    as_bad (_(".seh_handler requires a handler"));
> +
> +  SKIP_WHITESPACE ();
> +
> +  if (*input_line_pointer == '@')
> +    {
> +      name_end = get_symbol_name (&symbol_name);
> +
> +      seh_ctx_cur->handler.X_op = O_constant;
> +      seh_ctx_cur->handler.X_add_number = 0;
> +
> +      if (strcasecmp (symbol_name, "@0") == 0
> +	  || strcasecmp (symbol_name, "@null") == 0)
> +	;
> +      else if (strcasecmp (symbol_name, "@1") == 0)
> +	seh_ctx_cur->handler.X_add_number = 1;
> +      else
> +	as_bad (_("unknown constant value '%s' for handler"), symbol_name);
> +
> +      (void) restore_line_pointer (name_end);
> +    }
> +  else
> +    expression (&seh_ctx_cur->handler);
> +
> +  seh_ctx_cur->handler_data.X_op = O_constant;
> +  seh_ctx_cur->handler_data.X_add_number = 0;
> +  seh_ctx_cur->xdata_header.x = 1;
> +
> +  while (skip_whitespace_and_comma (0))
> +    {
> +      name_end = get_symbol_name (&symbol_name);
> +      (void) restore_line_pointer (name_end);
> +    }
> +}
> +
> +/* Switch to subsection for handler data for exception region.  */
> +static void
> +obj_coff_seh_handlerdata (const int what ATTRIBUTE_UNUSED)
> +{
> +  demand_empty_rest_of_line ();
> +
> +  switch_xdata (seh_ctx_cur->subsection + 1, seh_ctx_cur->code_seg);
> +}
> +
> +/* Obtain available unwind element.  */
> +static void
> +seh_aarch64_add_unwind_element (const seh_aarch64_unwind_types unwind_type,
> +				unsigned offset, unsigned reg)
> +{
> +  gas_assert (in_seh_proc);
> +
> +  const struct aarch64_unwind_code_pack_info *unwind_code_pack_info

This variable could just be called 'info'; the shorter name makes the rest of
the function much more readable.

> +    = aarch64_unwind_code_pack_data + unwind_type;
> +  unsigned value_offset_bits = 0;
> +
> +  if ((seh_ctx_cur->unwind_codes_byte_count
> +      + unwind_code_pack_info->size) > AARCH64_MAX_UNWIND_CODES_SIZE)
> +    as_bad (_("no unwind element available."));
> +
> +  seh_aarch64_unwind_code *aarch64_element;
> +  aarch64_element = seh_ctx_cur->unwind_codes
> +		    + seh_ctx_cur->unwind_codes_count++;
> +  aarch64_element->value = 0;

I think these four lines would be better placed at the end of the function
(after computing the value).  We also don't need to specify aarch64 in a local
variable name.

> +
> +  if (unwind_code_pack_info->offset_bits)
> +    {
> +      offset = (offset >> unwind_code_pack_info->offset_right_shift)
> +	       - unwind_code_pack_info->offset;
> +      offset &= (1 << unwind_code_pack_info->offset_bits) - 1;
> +      aarch64_element->value |= offset << value_offset_bits;
> +      value_offset_bits += unwind_code_pack_info->offset_bits;
> +    }
> +
> +  if (unwind_code_pack_info->reg_bits)
> +    {
> +      reg = (reg >> unwind_code_pack_info->reg_right_shift)
> +	    - unwind_code_pack_info->reg_offset;
> +      reg &= (1 << unwind_code_pack_info->reg_bits) - 1;
> +      aarch64_element->value |= reg << value_offset_bits;
> +      value_offset_bits += unwind_code_pack_info->reg_bits;
> +    }

Instead of silently masking out overflowing bits in offset/reg, I think it
would be better to explicitly check and reject.

> +
> +  if (unwind_code_pack_info->code_bits)
> +    {
> +      unsigned code = unwind_code_pack_info->code;
> +      code &= (1 << unwind_code_pack_info->code_bits) - 1;
> +      aarch64_element->value |= code << value_offset_bits;



I think the whole function flows a bit better if you build the value starting
from the code.  I'd suggest the following (to replace these three if blocks):

if (offset & ~(((1 << info->offset_bits) - 1) << info->offset_shift))
  as_bad (...)
if (reg & ~(((1 << info->reg_bits) - 1) << info->reg_shift))
  as_bad (...)

value = info->code;
if (info->reg_bits)
  value = (value << info->reg_bits)
	  | ((reg - info->reg_addend) >> info->reg_shift)
if (info->offset_bits)
  value = (value << info->offset_bits)
	  | ((offset - info->offset_addend) >> info->offset_shift)


(The 'if's are actually unnecessary here, but they might make the code a bit
clearer.  This also makes the 'code_bits' value unused, so you could drop that.)


> +    }
> +
> +  aarch64_element->type = unwind_type;
> +  seh_ctx_cur->unwind_codes_byte_count += unwind_code_pack_info->size;
> +}
> +
> +/* Mark begin of new context.  */
> +static void
> +obj_coff_seh_proc (const int what ATTRIBUTE_UNUSED)
> +{
> +  char *symbol_name;
> +  char name_end;
> +
> +  if (in_seh_proc)
> +    as_bad (_("previous SEH entry not closed (missing .seh_endproc)"));
> +
> +  if (*input_line_pointer == 0 || *input_line_pointer == '\n')
> +    as_bad (_(".seh_proc requires function label name"));
> +
> +
> +  if (!seh_ctx_root)
> +  {
> +    seh_ctx_root = XCNEW (seh_context);
> +    seh_ctx_cur = seh_ctx_root;
> +  }
> +  else
> +  {
> +    seh_ctx_cur->next = XCNEW (seh_context);
> +    seh_ctx_cur = seh_ctx_cur->next;
> +  }
> +
> +  seh_ctx_cur->next = NULL;
> +  seh_ctx_cur->code_seg = now_seg;
> +
> +  /* The current implementation always use a pair of .pdata and .xdata
> +     records.  */
> +  const bool use_xdata = true;
> +
> +  if (use_xdata)
> +    {
> +      x_segcur = seh_hash_find_or_make (seh_ctx_cur->code_seg, ".xdata");
> +      seh_ctx_cur->subsection = x_segcur->subseg;
> +      x_segcur->subseg += 2;
> +
> +      /* Initialize an empty .xdata record.  */
> +      seh_ctx_cur->unwind_codes_count = 0;
> +      seh_ctx_cur->unwind_codes_byte_count = 0;
> +      seh_ctx_cur->epilogue_scopes_count = 0;
> +      seh_ctx_cur->epilogue_scopes_capacity = 0;
> +      seh_ctx_cur->epilogue_scopes = NULL;
> +      seh_ctx_cur->xdata_header.x = 0;
> +    }
> +
> +  SKIP_WHITESPACE ();
> +
> +  name_end = get_symbol_name (&symbol_name);
> +  seh_ctx_cur->func_name = xstrdup (symbol_name);
> +  (void) restore_line_pointer (name_end);
> +
> +  demand_empty_rest_of_line ();
> +
> +  seh_ctx_cur->start_addr = symbol_temp_new_now ();
> +  in_seh_proc = true;
> +}
> +
> +/* Mark end of prologue for current context.  */
> +static void
> +obj_coff_seh_endprologue (const int what ATTRIBUTE_UNUSED)
> +{
> +  if (!verify_context (".seh_endprologue")
> +      || !seh_validate_seg (".seh_endprologue"))
> +    return;
> +  demand_empty_rest_of_line ();
> +
> +  if (seh_ctx_cur->endprologue_addr != NULL)
> +    as_warn (_("duplicate .seh_endprologue in .seh_proc block"));
> +  else
> +    seh_ctx_cur->endprologue_addr = symbol_temp_new_now ();
> +
> +  /* Unwind codes need to be reversed.  */
> +  for (unsigned i = 0, n = seh_ctx_cur->unwind_codes_count; i < n / 2; ++i)
> +    {
> +      seh_aarch64_unwind_code *unwind_codes = seh_ctx_cur->unwind_codes;
> +      const seh_aarch64_unwind_code temp = unwind_codes[i];
> +      unwind_codes[i] = unwind_codes[n-i-1];
> +      unwind_codes[n-i-1] = temp;
> +    }
> +
> +   seh_aarch64_add_unwind_element (unwind_end, 0, 0);
> +}
> +
> +/* Mark end of current context.  */
> +static void
> +obj_coff_seh_endproc (const int what ATTRIBUTE_UNUSED)
> +{
> +  demand_empty_rest_of_line ();
> +  if (!in_seh_proc)
> +    as_bad (_(".seh_endproc used without .seh_proc"));
> +
> +  seh_validate_seg (".seh_endproc");
> +
> +  seh_ctx_cur->end_addr = symbol_temp_new_now ();
> +  in_seh_proc = false;
> +}
> +
> +static void
> +obj_coff_seh_startepilogue (const int what ATTRIBUTE_UNUSED)
> +{
> +  if (!verify_context (".seh_startepilogue")
> +      || !seh_validate_seg (".seh_startepilogue"))
> +    return;
> +  demand_empty_rest_of_line ();
> +
> +  const unsigned max_epilogue_scopes = AARCH64_MAX_EPILOGUE_SCOPES;

This could be inlined into the condition.

> +  if (seh_ctx_cur->epilogue_scopes_count >= max_epilogue_scopes)
> +    as_bad (_("no epilogue scopes available."));
> +
> +  symbolS *epilogue_start_addr = symbol_temp_new_now ();
> +  expressionS exp;
> +  exp.X_op = O_subtract;
> +  exp.X_add_symbol = epilogue_start_addr;
> +  exp.X_op_symbol = seh_ctx_cur->start_addr;
> +  exp.X_add_number = 0;
> +
> +  if (!resolve_expression (&exp) || exp.X_op != O_constant
> +      || exp.X_add_number < 0)
> +    as_bad (_(".seh_startepilog offset expression for %s "
> +	    "does not evaluate to a non-negative constant"),
> +	    S_GET_NAME (epilogue_start_addr));
> +
> +  if (seh_ctx_cur->epilogue_scopes_count
> +      >= seh_ctx_cur->epilogue_scopes_capacity)
> +    {
> +      const unsigned initial_capacity = 32;

This variable could be inlined.

> +      if (seh_ctx_cur->epilogue_scopes_capacity)
> +	seh_ctx_cur->epilogue_scopes_capacity *= 2;
> +      else
> +	seh_ctx_cur->epilogue_scopes_capacity = initial_capacity;
> +
> +      seh_ctx_cur->epilogue_scopes
> +	= XRESIZEVEC (seh_aarch64_epilogue_scope, seh_ctx_cur->epilogue_scopes,
> +		     seh_ctx_cur->epilogue_scopes_capacity);
> +    }
> +
> +  seh_aarch64_epilogue_scope *epilogue_scope = seh_ctx_cur->epilogue_scopes
> +    + seh_ctx_cur->epilogue_scopes_count;
> +  epilogue_scope->epilogue_start_offset = exp.X_add_number / 4;
> +  epilogue_scope->reserved = 0;
> +  epilogue_scope->epilogue_start_index = seh_ctx_cur->unwind_codes_byte_count;
> +  seh_ctx_cur->epilogue_scopes_count++;
> +}
> +
> +static void
> +obj_coff_seh_endepilogue (const int what ATTRIBUTE_UNUSED)
> +{
> +  if (!verify_context (".seh_endepilogue")
> +      || !seh_validate_seg (".seh_endepilogue"))
> +    return;
> +
> +  demand_empty_rest_of_line ();
> +
> +  expressionS exp;
> +  symbolS *epilogue_end_addr = symbol_temp_new_now ();
> +  exp.X_op = O_subtract;
> +  exp.X_add_symbol = epilogue_end_addr;
> +  exp.X_op_symbol = seh_ctx_cur->start_addr;
> +  exp.X_add_number = 0;
> +
> +  if (!resolve_expression (&exp) || exp.X_op != O_constant
> +      || exp.X_add_number < 0)
> +    as_bad (_(".seh_endepilogue offset expression for %s "
> +	    "does not evaluate to a non-negative constant"),
> +	    S_GET_NAME (epilogue_end_addr));
> +
> +   seh_aarch64_epilogue_scope *epilogue_scope = seh_ctx_cur->epilogue_scopes
> +     + seh_ctx_cur->epilogue_scopes_count - 1;
> +
> +   epilogue_scope->epilogue_end_offset = exp.X_add_number;
> +
> +  /* End code.  */
> +  seh_aarch64_add_unwind_element (unwind_end, 0, 0);
> +}
> +
> +/* End-of-file hook.  */
> +static void
> +free_seh_ctx (struct seh_aarch64_context *seh_ctx)
> +{
> +  free (seh_ctx->func_name);
> +  const seh_aarch64_func_fragment *fragment = seh_ctx->func_fragment.next;
> +  while (fragment)
> +    {
> +      const seh_aarch64_func_fragment *next = fragment->next;
> +      XDELETE (fragment);
> +      fragment = next;
> +    }
> +  XDELETEVEC (seh_ctx->epilogue_scopes);
> +  free (seh_ctx);
> +}
> +
> +static void
> +obj_coff_seh_save_reg (const int type)
> +{
> +  gas_assert (type >= 0 && type <= unwind_last_type);
> +
> +  const struct aarch64_unwind_code_pack_info *unwind_code_pack_info
> +    = aarch64_unwind_code_pack_data + type;
> +
> +  if (!unwind_code_pack_info->directive
> +      || !seh_validate_seg (unwind_code_pack_info->directive))
> +    return;
> +
> +  SKIP_WHITESPACE ();
> +
> +  char *symbol_name = NULL;
> +  unsigned reg = -1;
> +
> +  if (unwind_code_pack_info->reg_bits)
> +    {
> +      char name_end = get_symbol_name (&symbol_name);

We should check the first character - is it required to match the register
name?

> +      reg = atoi (symbol_name + 1);
> +      (void) restore_line_pointer (name_end);
> +
> +      if (!skip_whitespace_and_comma (1))
> +	return;
> +
> +      if (reg > 30)
> +	as_bad (_("register number is out of range"));
> +    }
> +
> +  offsetT off = -1;
> +  if (unwind_code_pack_info->offset_bits)
> +    {
> +      off = get_absolute_expression ();
> +
> +      if (off < 0)
> +	as_bad (_("offset is negative"));
> +    }
> +
> +  demand_empty_rest_of_line ();
> +
> +  seh_aarch64_add_unwind_element (type, off, reg);
> +}
> +
> +/* Add a stack-allocation token to current context.  */
> +static void
> +obj_coff_seh_stackalloc (const int what ATTRIBUTE_UNUSED)
> +{
> +  const offsetT off = get_absolute_expression ();
> +  demand_empty_rest_of_line ();
> +
> +  /* aarch64 offset should be encoded in multiples of sixteen.  */
> +  if ((off & 0xf) != 0)
> +    as_bad (_(".seh_stackalloc offset < 16-byte stack alignment"));
> +
> +  if (off < 0x200)
> +    seh_aarch64_add_unwind_element (unwind_alloc_s, off, 0);
> +  else if (off < 0x8000)
> +    seh_aarch64_add_unwind_element (unwind_alloc_m, off, 0);
> +  else if (off < 0x10000000)
> +    seh_aarch64_add_unwind_element (unwind_alloc_l, off, 0);
> +  else
> +    as_bad (_(".seh_stackalloc offset out of range"));
> +}
> +
> +/* Data writing routines.  */
> +static void
> +seh_aarch64_emit_epilog_scopes (const seh_context *seh_ctx,
> +				const uint64_t fragment_offset,
> +				const unsigned prolog_size,
> +				const unsigned first_fragment_scope,
> +				const unsigned last_fragment_scope,
> +				const bool has_phantom_prolog)
> +{
> +  unsigned start_index_offset = 0;
> +  const seh_aarch64_epilogue_scope *scopes = seh_ctx->epilogue_scopes;
> +  if (first_fragment_scope < seh_ctx->epilogue_scopes_count)
> +    start_index_offset = scopes[first_fragment_scope].epilogue_start_index
> +			 - prolog_size;
> +  if (has_phantom_prolog)
> +    {
> +      if (start_index_offset == 0)
> +	as_bad (_("start index offset for the epilogue cannot be 0 when "
> +		"phantom prolog is used"));
> +      --start_index_offset;
> +    }
> +
> +  for (unsigned i = first_fragment_scope; i < last_fragment_scope; ++i)
> +    {
> +      seh_aarch64_epilogue_scope scope = seh_ctx->epilogue_scopes[i];
> +      scope.epilogue_start_offset_reduced = (scope.epilogue_start_offset
> +					    - fragment_offset) >> 2;
> +      scope.epilogue_start_index -= start_index_offset;
> +      uint32_t scope_code;
> +      memcpy (&scope_code, &scope, sizeof (scope_code));
> +      md_number_to_chars (frag_more (4), scope_code, 4);
> +    }
> +}
> +
> +static void
> +seh_aarch64_emit_unwind_codes (const seh_context *seh_ctx,
> +			       const unsigned prolog_size,
> +			       const unsigned first_epilog_index,
> +			       const unsigned last_epilog_index,
> +			       const bool has_phantom_prolog)
> +{
> +  unsigned total_byte_count = 0;
> +
> +  if (has_phantom_prolog)
> +    {
> +      ++total_byte_count;
> +      md_number_to_chars (frag_more (1), AARCH64_UNOP_ENDC, 1);
> +    }
> +
> +  unsigned unwind_bytes_offset = 0;
> +  for (unsigned i = 0; i < seh_ctx->unwind_codes_count; ++i)
> +    {
> +      const seh_aarch64_unwind_code *code = seh_ctx->unwind_codes
> +					    + i;

Why is this line wrapped?  I think up to 79 chars is ok.

> +      const unsigned byte_count
> +	= aarch64_unwind_code_pack_data[code->type].size;
> +      unwind_bytes_offset += byte_count;
> +
> +      if (unwind_bytes_offset > last_epilog_index)
> +	break;
> +
> +      if (unwind_bytes_offset > prolog_size
> +	  && unwind_bytes_offset <= first_epilog_index)
> +	continue;
> +
> +      /*  emit unwind code bytes in big endian.  */
> +      number_to_chars_bigendian (frag_more (byte_count), code->value,
> +				 byte_count);
> +      total_byte_count += byte_count;
> +    }
> +
> +    /* handle word alignment.  */

nit: Capital H

> +    unsigned required_padding = (4 - total_byte_count % 4) % 4;

This could just be:

+    unsigned required_padding = (- total_byte_count) % 4;

> +    if (required_padding)
> +      {
> +	/* Use AARCH64_UNOP_NOP for alignment.  */
> +	const uint32_t nop_chain = (AARCH64_UNOP_NOP << 24)
> +				   | (AARCH64_UNOP_NOP << 16)
> +				   | (AARCH64_UNOP_NOP << 8)
> +				   | AARCH64_UNOP_NOP;

How about removing the define and using:

+	/* Use the nop unwind code for alignment.  */
+	const uint32_t nop_chain = 0xe3e3e3e3;

> +
> +	md_number_to_chars (frag_more (required_padding), nop_chain,
> +			    required_padding);
> +      }
> +}
> +
> +static bool
> +seh_function_size (const struct seh_aarch64_context *seh_ctx,
> +		  uintptr_t *size)
> +{
> +  fragS *start_frag, *end_frag;
> +  addressT start_offset, end_offset;
> +  start_frag = symbol_get_frag_and_value (seh_ctx->start_addr, &start_offset);
> +  end_frag = symbol_get_frag_and_value (seh_ctx->end_addr, &end_offset);
> +
> +  intptr_t func_size = end_frag->fr_address + end_offset
> +		       - start_frag->fr_address - start_offset;
> +  if (func_size < 0)

Does this always hold when the function size "has not been evaluated"?

> +    return false;
> +
> +  *size = func_size;
> +  return true;
> +}
> +
> +/* Write out the xdata information for one function.  */
> +static void
> +seh_aarch64_write_function_xdata (struct seh_aarch64_context *seh_ctx)
> +{
> +  if (!seh_ctx->unwind_codes_byte_count)
> +    return;
> +
> +  const segT save_seg = now_seg;
> +  const subsegT save_subseg = now_subseg;
> +
> +  switch_xdata (seh_ctx->subsection, seh_ctx->code_seg);
> +
> +  /* Set 4-byte alignment.  */
> +  frag_align (2, 0, 0);
> +
> +  uintptr_t func_size = 0;
> +  if (!seh_function_size (seh_ctx, &func_size))
> +    {
> +      as_bad (_("the function size for %s has not been evaluated"),
> +	      seh_ctx->func_name);
> +      return;
> +    }
> +
> +  /* The large functions should be split into fragments smaller than 1MB with
> +     4 bytes alignment.
> +     https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170#large-functions.  */
> +  const unsigned max_frag_size = (1 << 20) - 4;
> +  const bool is_fragmented_function = func_size > max_frag_size;
> +
> +  /* [first_fragment_scope, last_fragment_scope).  */

This comment is in the wrong place.

> +  unsigned prolog_insn_count = 0;
> +  for (unsigned i = 0; i < seh_ctx->unwind_codes_count; ++i)
> +    {
> +      if (seh_ctx->unwind_codes[i].type == unwind_end)
> +	{
> +	  prolog_insn_count = i + 1;
> +	  break;
> +	}
> +    }
> +
> +  unsigned prolog_size;
> +  if (seh_ctx->epilogue_scopes_count)
> +    prolog_size = seh_ctx->epilogue_scopes[0].epilogue_start_index;
> +  else
> +    prolog_size = seh_ctx->unwind_codes_byte_count;
> +
> +  seh_aarch64_func_fragment *fragment;
> +  fragment = &seh_ctx->func_fragment;
> +  uintptr_t fragment_offset = 0;
> +  unsigned first_fragment_scope = 0;
> +  unsigned last_fragment_scope = 0;
> +  while (true)
> +    {
> +      fragment->xdata_addr = symbol_temp_new_now ();
> +      fragment->offset = fragment_offset;
> +      fragment->next = NULL;
> +
> +      uintptr_t frag_size = func_size - fragment_offset;

I think it would be marginally better to use a frag_end variable instead of
frag_size.

> +      if (frag_size > max_frag_size)
> +	frag_size = max_frag_size;
> +
> +      const bool is_first_frag = fragment_offset == 0;
> +      const bool is_last_frag = (fragment_offset + frag_size) == func_size;
> +
> +      if (!is_fragmented_function)
> +	last_fragment_scope = seh_ctx->epilogue_scopes_count;
> +      else
> +	{
> +	  first_fragment_scope = last_fragment_scope;
> +	  for (unsigned i = first_fragment_scope;
> +	       i < seh_ctx->epilogue_scopes_count; ++i)
> +	    {
> +	      const seh_aarch64_epilogue_scope *scope
> +		= seh_ctx->epilogue_scopes;
> +	      scope += i;
> +	      if (scope->epilogue_start_offset >= (fragment_offset + frag_size))
> +		break;
> +
> +	      if (scope->epilogue_end_offset >= (fragment_offset + frag_size))
> +		{
> +		  frag_size = scope->epilogue_start_offset - fragment_offset;
> +		  break;
> +		}
> +
> +	      if (scope->epilogue_start_offset >= fragment_offset)
> +		last_fragment_scope = i + 1;
> +	    }
> +	}

You're missing handling for too many epilogs or too many code words.

> +
> +      seh_aarch64_xdata_header *header = &seh_ctx->xdata_header;
> +      const seh_aarch64_epilogue_scope *scopes = seh_ctx->epilogue_scopes;
> +
> +      const uint32_t func_length_encoded = frag_size >> 2;
> +      header->func_length = func_length_encoded;
> +      header->vers = 0;
> +      header->e = 0;
> +      header->code_words = 0;
> +      header->epilogue_count = 0;
> +
> +      header->ext_code_words = 0;
> +      header->ext_epilogue_count = last_fragment_scope
> +				   - first_fragment_scope;
> +      header->reserved = 0;
> +
> +      unsigned first_epilog_index = 0;
> +      unsigned last_epilog_index = 0;
> +      if (!header->ext_epilogue_count)
> +	{
> +	  first_epilog_index = prolog_size;
> +	  last_epilog_index = prolog_size;
> +	}
> +      else
> +	{
> +	  const seh_aarch64_epilogue_scope *scope;
> +	  scope = scopes + first_fragment_scope;
> +	  first_epilog_index = scope->epilogue_start_index;
> +	  if (last_fragment_scope == seh_ctx->epilogue_scopes_count)
> +	    last_epilog_index = seh_ctx->unwind_codes_byte_count;
> +	  else
> +	    {
> +	      scope = scopes + last_fragment_scope;
> +	      last_epilog_index = scope->epilogue_start_index;
> +	    }
> +	}
> +
> +      unsigned unwind_bytes = 0;
> +      if (is_first_frag || is_last_frag)
> +	unwind_bytes += prolog_size;
> +
> +      if (header->ext_epilogue_count)
> +	unwind_bytes += last_epilog_index - first_epilog_index;
> +
> +      const bool has_phantom_prolog = is_fragmented_function && is_last_frag;
> +      if (has_phantom_prolog && unwind_bytes)
> +	{
> +	  /* One more epilogue scope and unwind code are emitted with phantom
> +	     prolog.  */
> +	  unwind_bytes += 1;
> +	  ++header->ext_epilogue_count;
> +	}
> +
> +      /* Calculate the number of code words with 4-byte alignment.  */
> +      header->ext_code_words = (unwind_bytes + 3) / 4;
> +
> +      if ((header->ext_code_words == 0 && header->ext_epilogue_count == 0)
> +	  || header->ext_code_words > 31
> +	  || header->ext_epilogue_count > 31)
> +	md_number_to_chars (frag_more (8),
> +			   seh_ctx->xdata_header_value, 8);
> +      else
> +	{
> +	  header->code_words = header->ext_code_words;
> +	  header->epilogue_count = header->ext_epilogue_count;
> +	  if (header->epilogue_count == 1)
> +	    {
> +	      header->e = 1;
> +	      if (has_phantom_prolog)
> +		header->ext_epilogue_count = 0;
> +	      else
> +		{
> +		  const seh_aarch64_epilogue_scope *scope;
> +		  scope = scopes + first_fragment_scope;
> +		  header->ext_epilogue_count = scope->epilogue_start_index;
> +		}
> +	    }
> +	  md_number_to_chars (frag_more (4),
> +			      seh_ctx->xdata_header_value, 4);
> +	}

I think it might be clearer if this header emission were split off into a
separated function.  It might also help to avoid tying the internal struct
layout to the on disk format (and perhaps just passing it as separate
arguments).

> +
> +      if (header->ext_epilogue_count && !header->e)
> +	{
> +	  seh_aarch64_emit_epilog_scopes (seh_ctx,
> +					 fragment_offset, prolog_size,
> +					 first_fragment_scope,
> +					 last_fragment_scope,
> +					 has_phantom_prolog);
> +	  if (has_phantom_prolog)
> +	    {
> +	      const uint32_t epilog_start_index_encoded = 1 << 22;
> +	      const uint32_t epilog_start_offset_encoded
> +		= (frag_size - prolog_insn_count * 4) >> 2;
> +	      md_number_to_chars (frag_more (4),
> +				  epilog_start_index_encoded
> +				  | epilog_start_offset_encoded, 4);
> +	    }
> +	}
> +
> +      if (header->ext_code_words)
> +	seh_aarch64_emit_unwind_codes (seh_ctx, prolog_size, first_epilog_index,
> +				       last_epilog_index, has_phantom_prolog);
> +
> +      if (header->x == 1)
> +	{
> +	  if (seh_ctx->handler.X_op == O_symbol)
> +	    seh_ctx->handler.X_op = O_symbol_rva;
> +
> +	  emit_expr (&seh_ctx->handler, 4);
> +	}
> +
> +      fragment_offset += frag_size;
> +      if (fragment_offset == func_size)
> +	break;
> +
> +      fragment->next = XCNEW (seh_aarch64_func_fragment);
> +      fragment = fragment->next;
> +    }
> +
> +  subseg_set (save_seg, save_subseg);

I've struggled to follow this function; please add some inline comments to explain the flow (and/ore refactor it to make the flow clearer).

> +}
> +
> +/* Write out pdata for one function.  */
> +static void
> +seh_aarch64_write_function_pdata (const seh_context *seh_ctx)
> +{
> +  expressionS exp;
> +  const segT save_seg = now_seg;
> +  const subsegT save_subseg = now_subseg;
> +  memset (&exp, 0, sizeof (expressionS));
> +  switch_pdata (seh_ctx->code_seg);
> +
> +  if (seh_ctx->unwind_codes_byte_count)
> +    {
> +      const seh_aarch64_func_fragment *fragment = &seh_ctx->func_fragment;
> +      while (fragment)
> +	{
> +	  exp.X_op = O_symbol_rva;
> +	  exp.X_add_number = fragment->offset;
> +	  exp.X_add_symbol = seh_ctx->start_addr;
> +	  emit_expr (&exp, 4);
> +
> +	  exp.X_op = O_symbol_rva;
> +	  /* TODO: Implementing packed unwind data.  */
> +	  exp.X_add_number = 0;
> +	  exp.X_add_symbol = fragment->xdata_addr;
> +	  emit_expr (&exp, 4);
> +	  fragment = fragment->next;
> +	}
> +    }
> +
> +  subseg_set (save_seg, save_subseg);
> +}
> +
> +void
> +seh_aarch64_write_data (void)
> +{
> +  if (in_seh_proc)
> +    as_bad (_("open SEH entry at end of file (missing .seh_endproc)"));
> +
> +  if (!seh_ctx_root)
> +    return;
> +
> +  struct seh_aarch64_context *seh_ctx = seh_ctx_root;
> +  seh_ctx_root = NULL;
> +
> +  /* Relax the segment to be able to calculate the function sizes.  */
> +  subsegs_finish_section (seh_ctx->code_seg);
> +  const segment_info_type *seginfo = seg_info (seh_ctx->code_seg);
> +  relax_segment (seginfo->frchainP->frch_root, seh_ctx->code_seg, 0);
> +
> +  while (seh_ctx)
> +  {
> +    seh_aarch64_write_function_xdata (seh_ctx);
> +    seh_aarch64_write_function_pdata (seh_ctx);
> +    struct seh_aarch64_context *next = seh_ctx->next;
> +    free_seh_ctx (seh_ctx);
> +    seh_ctx = next;
> +  }
> +}
> +
> +void
> +obj_coff_seh_do_final (void)
> +{
> +}
> diff --git a/gas/config/obj-coff-seh-aarch64.h b/gas/config/obj-coff-seh-aarch64.h
> new file mode 100644
> index 00000000000..bda7e00adc5
> --- /dev/null
> +++ b/gas/config/obj-coff-seh-aarch64.h
> @@ -0,0 +1,265 @@
> +/* SEH .pdata/.xdata COFF object file format on AArch64
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +
> +   This file is part of GAS.
> +
> +   GAS is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3, or (at your option)
> +   any later version.
> +
> +   GAS is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with GAS; see the file COPYING.  If not, write to the Free
> +   Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
> +   02110-1301, USA.  */
> +
> +/* SEH COFF AArch64 implementation partially intersects with the x64
> +   version, however it has a different extension to the unwind codes.
> +   It emits SEH data to pdata and xdata sections.  In some cases SEH
> +   data could be emitted to a packed record in the pdata section
> +   without the need for data in the xdata section.  However, the packed
> +   pdata record is not implemented yet.  */

Can we have a separate list of things that aren't yet supported (at least as a
separate paragraph).  As well as packed unwind data, I think this includes:
- Support for SVE/SME (at least, I think that's what the unimplemented unwind
  codes are used for);
- Reuse of identical unwind code sequences.

> +
> +#ifndef OBJ_COFF_SEH_AARCH64_H
> +#define OBJ_COFF_SEH_AARCH64_H
> +
> +typedef enum seh_aarch64_unwind_types
> +{
> +  unwind_alloc_s,
> +  unwind_alloc_m,
> +  unwind_alloc_l,
> +  unwind_save_reg,
> +  unwind_save_reg_x,
> +  unwind_save_regp,
> +  unwind_save_regp_x,
> +  unwind_save_fregp,
> +  unwind_save_fregp_x,
> +  unwind_save_freg,
> +  unwind_save_freg_x,
> +  unwind_save_lrpair,
> +  unwind_save_fplr,
> +  unwind_save_fplr_x,
> +  unwind_save_r19r20_x,
> +  unwind_add_fp,
> +  unwind_set_fp,
> +  unwind_save_next,
> +  unwind_nop,
> +  unwind_pac_sign_lr,
> +  unwind_end,
> +  unwind_end_c,
> +  unwind_last_type = unwind_end_c
> +} seh_aarch64_unwind_types;
> +
> +#define SEH_CMDS						      \
> +  /* Start a function that contains SEH.  */			      \
> +  {"seh_proc", obj_coff_seh_proc, 0},				      \
> +								      \
> +  /* End a function that contains SEH.  */			      \
> +  {"seh_endproc", obj_coff_seh_endproc, 0},			      \
> +								      \
> +  /* End a SEH prolog with unwinding codes.  */			      \
> +  {"seh_endprologue", obj_coff_seh_endprologue, 0},		      \
> +								      \
> +  /* Allocate stack.  */					      \
> +  {"seh_stackalloc", obj_coff_seh_stackalloc, 0},		      \
> +								      \
> +  /* Set a SEH handler.  */					      \
> +  {"seh_handler", obj_coff_seh_handler, 0},			      \
> +								      \
> +  /* Set a SEH handler data.  */				      \
> +  {"seh_handlerdata", obj_coff_seh_handlerdata, 0},		      \
> +								      \
> +  /* Start a SEH epilogue.  */					      \
> +  {"seh_startepilogue", obj_coff_seh_startepilogue, 0},		      \
> +								      \
> +  /* End a SEH epilogue.  */					      \
> +  {"seh_endepilogue", obj_coff_seh_endepilogue, 0},		      \
> +								      \
> +  /* Save an 'x' register.  */					      \
> +  {"seh_save_reg", obj_coff_seh_save_reg, unwind_save_reg},	      \
> +								      \
> +  /* Save an 'x' register with a pre-indexed offset.  */	      \
> +  {"seh_save_reg_x", obj_coff_seh_save_reg, unwind_save_reg_x},	      \
> +								      \
> +  /* Save an 'x' register pair.  */				      \
> +  {"seh_save_regp", obj_coff_seh_save_reg, unwind_save_regp},	      \
> +								      \
> +  /* Save an 'x' register pair with a pre-indexed offset.  */	      \
> +  {"seh_save_regp_x", obj_coff_seh_save_reg, unwind_save_regp_x},     \
> +								      \
> +  /* Save an 'x' register and lr.  */				      \
> +  {"seh_save_lrpair", obj_coff_seh_save_reg, unwind_save_lrpair},     \
> +								      \
> +  /* Save a 'd' register pair.  */				      \
> +  {"seh_save_fregp", obj_coff_seh_save_reg, unwind_save_fregp},	      \
> +								      \
> +  /* Save a 'd' register pair with a pre-indexed offset.  */	      \
> +  {"seh_save_fregp_x", obj_coff_seh_save_reg, unwind_save_fregp_x},   \
> +								      \
> +  /* Save a 'd' register.  */					      \
> +  {"seh_save_freg", obj_coff_seh_save_reg, unwind_save_freg},	      \
> +								      \
> +  /* Save a 'd' register with a pre-indexed offset.  */		      \
> +  {"seh_save_freg_x", obj_coff_seh_save_reg, unwind_save_freg_x},     \
> +								      \
> +  /* Save fp and lr registers.  */				      \
> +  {"seh_save_fplr", obj_coff_seh_save_reg, unwind_save_fplr},	      \
> +								      \
> +  /* Save fp and lr registers with a pre-indexed offset.  */	      \
> +  {"seh_save_fplr_x", obj_coff_seh_save_reg, unwind_save_fplr_x},     \
> +								      \
> +  /* Save x19 and x20 registers with a pre-indexed offset.  */	      \
> +  {"seh_save_r19r20_x", obj_coff_seh_save_reg, unwind_save_r19r20_x}, \
> +								      \
> +  /* Set fp by sp + offset.  */					      \
> +  {"seh_add_fp", obj_coff_seh_save_reg, unwind_add_fp},		      \
> +								      \
> +  /* Unwind operation is not required.  */			      \
> +  {"seh_nop", obj_coff_seh_save_reg, unwind_nop},		      \
> +								      \
> +  /* Sign the return address in lr with pacibsp.  */		      \
> +  {"seh_pac_sign_lr", obj_coff_seh_save_reg, unwind_pac_sign_lr},     \
> +								      \
> +  /* Set fp by sp.  */						      \
> +  {"seh_set_fp", obj_coff_seh_save_reg, unwind_set_fp},		      \
> +								      \
> +  /* Save next register pair.  */				      \
> +  {"seh_save_next", obj_coff_seh_save_reg, unwind_save_next},
> +
> +/* AArch64 exceptions handling and unwinding structures.
> +   https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling#pdata-records.  */
> +
> +typedef struct seh_aarch64_unwind_code
> +{
> +  unsigned value;
> +  seh_aarch64_unwind_types type;
> +} seh_aarch64_unwind_code;
> +
> +typedef struct seh_aarch64_packed_unwind_data
> +{
> +  uint32_t flag : 2;
> +  uint32_t func_length : 11;
> +  uint32_t frame_size : 9;
> +  uint32_t cr : 2;
> +  uint32_t h : 1;
> +  uint32_t regI : 4;
> +  uint32_t regF : 3;
> +} seh_aarch64_packed_unwind_data;
> +
> +typedef struct seh_aarch64_except_info
> +{
> +  uint32_t flag : 2;
> +  uint32_t except_info_rva : 30;
> +} seh_aarch64_except_info;
> +
> +typedef union seh_aarch64_unwind_info
> +{
> +  seh_aarch64_except_info except_info;
> +  seh_aarch64_packed_unwind_data packed_unwind_data;
> +} seh_aarch64_unwind_info;
> +
> +typedef struct seh_aarch64_xdata_header
> +{
> +  uint32_t func_length : 18;
> +  uint32_t vers : 2;
> +  uint32_t x : 1;
> +  uint32_t e : 1;
> +  uint32_t epilogue_count : 5;
> +  uint32_t code_words : 5;
> +  uint32_t ext_epilogue_count : 16;
> +  uint32_t ext_code_words : 8;
> +  uint32_t reserved : 8;
> +} seh_aarch64_xdata_header;
> +
> +typedef struct seh_aarch64_epilogue_scope
> +{
> +  uint32_t epilogue_start_offset_reduced : 18;
> +  uint32_t reserved : 4;
> +  uint32_t epilogue_start_index : 10;
> +  bfd_vma epilogue_start_offset;
> +  bfd_vma epilogue_end_offset;
> +} seh_aarch64_epilogue_scope;
> +
> +typedef struct seh_aarch64_func_fragment
> +{
> +  bfd_vma offset;
> +  symbolS *xdata_addr;
> +  struct seh_aarch64_func_fragment *next;
> +} seh_aarch64_func_fragment;
> +
> +/* AARCH64_MAX_UNWIND_CODES is limited by
> +   seh_aarch64_xdata_header::ext_code_words.  */
> +#define AARCH64_MAX_UNWIND_CODES (255 * 4)
> +#define AARCH64_MAX_UNWIND_CODES_SIZE (255 * 4)
> +/* AARCH64_MAX_EPILOGUE_SCOPES is limited by
> +   seh_aarch64_xdata_header::ext_epilogue_count.  */
> +#define AARCH64_MAX_EPILOGUE_SCOPES 65535
> +
> +typedef struct seh_aarch64_context
> +{
> +  struct seh_aarch64_context *next;
> +
> +  /* Initial code-segment.  */
> +  segT code_seg;
> +  /* Function name.  */
> +  char *func_name;
> +  /* BeginAddress.  */
> +  symbolS *start_addr;
> +  /* EndAddress.  */
> +  symbolS *end_addr;
> +  /* PrologueEnd.  */
> +  symbolS *endprologue_addr;
> +  /* ExceptionHandler.  */
> +  expressionS handler;
> +  /* ExceptionHandlerData.  */
> +  expressionS handler_data;
> +
> +  subsegT subsection;
> +
> +  union {
> +    seh_aarch64_xdata_header xdata_header;
> +    valueT xdata_header_value;
> +  };

I think this type punning is incorrect for a big endian host, and there might
be other portability issues beyond this.  Type punning to a struct of two
uint32_t values is more likely to work, but that's still not guaranteed by the
C standard to be correct, so it might be best just to build uint32_t values
from the individual bitfields piece by piece.

> +  unsigned unwind_codes_count;
> +  unsigned unwind_codes_byte_count;
> +  seh_aarch64_unwind_code unwind_codes[AARCH64_MAX_UNWIND_CODES];
> +  unsigned epilogue_scopes_count;
> +  unsigned epilogue_scopes_capacity;
> +  seh_aarch64_epilogue_scope *epilogue_scopes;
> +  expressionS except_handler;
> +  expressionS except_handler_data;
> +  /* The function fragments.  */
> +  seh_aarch64_func_fragment func_fragment;
> +} seh_context;
> +
> +/* aarch64 unwind code prefixes.  */
> +
> +#define AARCH64_UNOP_ALLOCS	  0b000U
> +#define AARCH64_UNOP_SAVER19R20X  0b001U
> +#define AARCH64_UNOP_SAVEFPLR	  0b01U
> +#define AARCH64_UNOP_SAVEFPLRX	  0b10U
> +#define AARCH64_UNOP_ALLOCM	  0b11000U
> +#define AARCH64_UNOP_SAVEREGP	  0b110010U
> +#define AARCH64_UNOP_SAVEREGPX	  0b110011U
> +#define AARCH64_UNOP_SAVEREG	  0b110100U
> +#define AARCH64_UNOP_SAVEREGX	  0b1101010U
> +#define AARCH64_UNOP_SAVELRPAIR	  0b1101011U
> +#define AARCH64_UNOP_SAVEFREGP	  0b1101100U
> +#define AARCH64_UNOP_SAVEFREGPX	  0b1101101U
> +#define AARCH64_UNOP_SAVEFREG	  0b1101110U
> +#define AARCH64_UNOP_SAVEFREGX	  0b11011110U
> +#define AARCH64_UNOP_ALLOCL	  0b11100000U
> +#define AARCH64_UNOP_SETFP	  0b11100001U
> +#define AARCH64_UNOP_ADDFP	  0b11100010U
> +#define AARCH64_UNOP_NOP	  0b11100011U
> +#define AARCH64_UNOP_END	  0b11100100U
> +#define AARCH64_UNOP_ENDC	  0b11100101U
> +#define AARCH64_UNOP_SAVENEXT	  0b11100110U
> +#define AARCH64_UNOP_PACSIGNLR	  0b11111100U
> +
> +#endif /* OBJ_COFF_SEH_AARCH64_H.  */
> diff --git a/gas/config/obj-coff-seh-shared.c b/gas/config/obj-coff-seh-shared.c
> index f3afd981aca..132470c70e6 100644
> --- a/gas/config/obj-coff-seh-shared.c
> +++ b/gas/config/obj-coff-seh-shared.c
> @@ -19,8 +19,13 @@
>     Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
>     02110-1301, USA.  */
>  
> +#if defined (COFFAARCH64)
> +#include "obj-coff-seh-aarch64.h"
> +typedef struct seh_aarch64_context seh_context_t;
> +#else
>  #include "obj-coff-seh.h"
>  typedef struct seh_context seh_context_t;
> +#endif
>  
>  /* Private segment collection list.  */
>  struct seh_seg_list {
> diff --git a/gas/config/obj-coff.c b/gas/config/obj-coff.c
> index 7732c0af911..b08295019d8 100644
> --- a/gas/config/obj-coff.c
> +++ b/gas/config/obj-coff.c
> @@ -55,7 +55,11 @@ static const char weak_altprefix[] = ".weak.";
>  #endif /* TE_PE */
>  
>  #include "obj-coff-seh-shared.c"
> +#if defined (COFFAARCH64)
> +#include "obj-coff-seh-aarch64.c"
> +#else
>  #include "obj-coff-seh.c"
> +#endif
>  
>  typedef struct
>    {
> diff --git a/gas/config/tc-aarch64.c b/gas/config/tc-aarch64.c
> index cd76163488c..d9d3296a458 100644
> --- a/gas/config/tc-aarch64.c
> +++ b/gas/config/tc-aarch64.c
> @@ -10375,6 +10375,16 @@ aarch64_cleanup (void)
>      }
>  }
>  
> +#if defined (OBJ_COFF)
> +/* Called after all assembly has been done.  */
> +
> +void
> +aarch64_md_finish (void)
> +{
> +  seh_aarch64_write_data();
> +}
> +#endif /* OBJ_COFF.  */
> +
>  #ifdef OBJ_ELF
>  /* Remove any excess mapping symbols generated for alignment frags in
>     SEC.  We may have created a mapping symbol before a zero byte
> diff --git a/gas/config/tc-aarch64.h b/gas/config/tc-aarch64.h
> index d1fb4c9058b..67f476a3dcc 100644
> --- a/gas/config/tc-aarch64.h
> +++ b/gas/config/tc-aarch64.h
> @@ -82,6 +82,11 @@ struct aarch64_fix
>  
>  #define tc_frob_section(S) aarch64_frob_section (S)
>  
> +#if defined (OBJ_COFF)
> +#define md_finish aarch64_md_finish
> +extern void aarch64_md_finish (void);
> +#endif
> +
>  /* The key used to sign a function's return address.  */
>  enum pointer_auth_key {
>    AARCH64_PAUTH_KEY_A,
> @@ -363,6 +368,7 @@ extern void aarch64_handle_align (struct frag *);
>  extern int tc_aarch64_regname_to_dw2regnum (char *regname);
>  extern void tc_aarch64_frame_initial_instructions (void);
>  extern bool aarch64_fix_adjustable (struct fix *);
> +extern void seh_aarch64_write_data (void);

Should this declaration be gated by OBJ_COFF?

Alice

>  
>  #ifdef TE_PE
>  
> diff --git a/gas/write.c b/gas/write.c
> index 9d0777051dd..903f858b4dc 100644
> --- a/gas/write.c
> +++ b/gas/write.c
> @@ -1831,7 +1831,7 @@ set_symtab (void)
>  #endif
>  #endif
>  
> -static void
> +void
>  subsegs_finish_section (asection *s)
>  {
>    struct frchain *frchainP;
> diff --git a/gas/write.h b/gas/write.h
> index 7281930c2d6..0638e5958d6 100644
> --- a/gas/write.h
> +++ b/gas/write.h
> @@ -188,5 +188,6 @@ extern fixS *fix_new_exp (fragS *, unsigned long, unsigned long,
>  			  const expressionS *, int, bfd_reloc_code_real_type);
>  extern void write_print_statistics (FILE *);
>  extern void as_bad_subtract (fixS *);
> +extern void subsegs_finish_section (asection *);
>  
>  #endif /* __write_h__ */
> -- 
> 2.50.1
>
  
Alice Carlotti April 16, 2026, 10:29 a.m. UTC | #3
On Thu, Apr 16, 2026 at 03:38:49AM +0100, Alice Carlotti wrote:
> On Thu, Mar 19, 2026 at 12:16:38PM +0100, Evgeny Karpov wrote:
> 
> Apologies for the long wait - it's taken me a while to get to grips with the
> spec and implementation in detail.
> 
> > The patch reuses shared helpers for SEH and implements SEH on AArch64.
> > The implementation is based on
> > (https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170)
> > and pdata/xdata SEH records are emitted from md_finish.
> > 
> > When .pdata/.xdata is emitted, the function size is required.
> > Function sizes are calculated as late as possible, and the code segment needs
> > to be relaxed to be able to calculate the function sizes.
> > 
> > Initially, obj_coff_generate_pdata was declared in write_object_file.
> 
> I think you mean "called in write_object_file".
> 
> > Before the change, obj_coff_generate_pdata was used only to validate
> > syntax, which was sufficient for that purpose. However, that location
> > seems incorrect, as it is too late to emit .pdata/.xdata records
> > in the AArch64 case.
> > 
> > md_finish has been declared for AArch64 and extended with
> > seh_aarch64_write_data to emit .pdata/.xdata records after all
> > assembly has been completed.
> > 
> > Signed-off-by: Evgeny Karpov <evgeny@kmaps.co>
> 
> Some general remarks:
> 
> - Epilogue/epilog and prologue/prolog in various names.  We should be
>   consistent with Microsoft's description, so remove the "ue"s.

Actually, I've changed my thoughts on this - we should be consistent with the
existing Binutils usage (and the GCC coding convention matches this).  So add
the "ue"s wherever they are missing.

> 
> - It hard to know whether the input parsing is correct when this patch doesn't
>   include any tests or documentation for the assembler directives.
> 
...
> > +/* Write out the xdata information for one function.  */
> > +static void
> > +seh_aarch64_write_function_xdata (struct seh_aarch64_context *seh_ctx)
> > +{
...
> > +
> > +      unsigned unwind_bytes = 0;
> > +      if (is_first_frag || is_last_frag)
> > +	unwind_bytes += prolog_size;
> > +
> > +      if (header->ext_epilogue_count)
> > +	unwind_bytes += last_epilog_index - first_epilog_index;
> > +
> > +      const bool has_phantom_prolog = is_fragmented_function && is_last_frag;

Don't we have a phantom prolog whenever we're not in the first fragment (where
I assume the real prolog exists)?

It might be help run your testing with the fragment size artificially limited
to a much smaller number, so that some functions are split into three or more
fragments.  Otherwise I suspect the fragmentation code is likely to be
untested.

Alice

> > +      if (has_phantom_prolog && unwind_bytes)
> > +	{
> > +	  /* One more epilogue scope and unwind code are emitted with phantom
> > +	     prolog.  */
> > +	  unwind_bytes += 1;
> > +	  ++header->ext_epilogue_count;
> > +	}
> > +
...
  
Martin Storsjö April 16, 2026, 11:37 a.m. UTC | #4
On Thu, 16 Apr 2026, Alice Carlotti wrote:

> On Thu, Apr 16, 2026 at 03:38:49AM +0100, Alice Carlotti wrote:
>> On Thu, Mar 19, 2026 at 12:16:38PM +0100, Evgeny Karpov wrote:
>>
>> - Epilogue/epilog and prologue/prolog in various names.  We should be
>>   consistent with Microsoft's description, so remove the "ue"s.
>
> Actually, I've changed my thoughts on this - we should be consistent with the
> existing Binutils usage (and the GCC coding convention matches this).  So add
> the "ue"s wherever they are missing.

This sounds reasonable to me.

In particular, for the assembler directives (.seh_endprologue, 
.seh_startepilogue, .seh_endepilogue), we need to stick to the current 
spelling for compatibility with the existing corresponding directives in 
LLVM (which were modelled after the preexisting ones for x86_64 as defined 
by binutils).

// Martin
  

Patch

diff --git a/gas/config/obj-coff-seh-aarch64.c b/gas/config/obj-coff-seh-aarch64.c
new file mode 100644
index 00000000000..6b5354f6544
--- /dev/null
+++ b/gas/config/obj-coff-seh-aarch64.c
@@ -0,0 +1,889 @@ 
+/* SEH .pdata/.xdata COFF object file format on AArch64
+   Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This file is part of GAS.
+
+   GAS is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   GAS is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with GAS; see the file COPYING.  If not, write to the Free
+   Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
+   02110-1301, USA.  */
+
+#include "obj-coff-seh-aarch64.h"
+
+static struct seh_aarch64_context *seh_ctx_root = NULL;
+static bool in_seh_proc = false;
+
+struct aarch64_unwind_code_pack_info {
+  const char *directive;
+  unsigned offset_bits;
+  unsigned reg_bits;
+  unsigned code_bits;
+  unsigned code;
+  unsigned offset_right_shift;
+  unsigned offset;
+  unsigned reg_right_shift;
+  unsigned reg_offset;
+  unsigned size;
+};
+
+static const struct aarch64_unwind_code_pack_info
+aarch64_unwind_code_pack_data[] = {
+/* Unwind codes packing for AArch64 is described at
+   https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170#unwind-codes
+   and calculated in seh_aarch64_add_unwind_element function.  */
+  {
+    .directive = NULL, .offset_bits = 5, .reg_bits = 0,
+    .code_bits = 3, .code = AARCH64_UNOP_ALLOCS, .offset_right_shift = 4,
+    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
+  },
+  {
+    .directive = NULL, .offset_bits = 11, .reg_bits = 0,
+    .code_bits = 5, .code = AARCH64_UNOP_ALLOCM, .offset_right_shift = 4,
+    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 2
+  },
+  {
+    .directive = NULL, .offset_bits = 24, .reg_bits = 0,
+    .code_bits = 8, .code = AARCH64_UNOP_ALLOCL, .offset_right_shift = 4,
+    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 4
+  },
+  {
+    .directive = ".seh_save_reg", .offset_bits = 6, .reg_bits = 4,
+    .code_bits = 6, .code = AARCH64_UNOP_SAVEREG, .offset_right_shift = 3,
+    .offset = 0, .reg_right_shift = 0, .reg_offset = 19, .size = 2
+  },
+  {
+    .directive = ".seh_save_reg_x", .offset_bits = 5, .reg_bits = 4,
+    .code_bits = 7, .code = AARCH64_UNOP_SAVEREGX, .offset_right_shift = 3,
+    .offset = 1, .reg_right_shift = 0, .reg_offset = 19, .size = 2
+  },
+  {
+    .directive = ".seh_save_regp", .offset_bits = 6, .reg_bits = 4,
+    .code_bits = 6, .code = AARCH64_UNOP_SAVEREGP, .offset_right_shift = 3,
+    .offset = 0, .reg_right_shift = 0, .reg_offset = 19, .size = 2
+  },
+  {
+    .directive = ".seh_save_regp_x", .offset_bits = 6, .reg_bits = 4,
+    .code_bits = 6, .code = AARCH64_UNOP_SAVEREGPX, .offset_right_shift = 3,
+    .offset = 1, .reg_right_shift = 0, .reg_offset = 19, .size = 2
+  },
+  {
+    .directive = ".seh_save_fregp", .offset_bits = 6, .reg_bits = 3,
+    .code_bits = 7, .code = AARCH64_UNOP_SAVEFREGP, .offset_right_shift = 3,
+    .offset = 0, .reg_right_shift = 0, .reg_offset = 8, .size = 2
+  },
+  {
+    .directive = ".seh_save_fregp_x", .offset_bits = 6, .reg_bits = 3,
+    .code_bits = 7, .code = AARCH64_UNOP_SAVEFREGPX, .offset_right_shift = 3,
+    .offset = 1, .reg_right_shift = 0, .reg_offset = 8, .size = 2
+  },
+  {
+    .directive = ".seh_save_freg", .offset_bits = 6, .reg_bits = 3,
+    .code_bits = 7, .code = AARCH64_UNOP_SAVEFREG, .offset_right_shift = 3,
+    .offset = 0, .reg_right_shift = 0, .reg_offset = 8, .size = 2
+  },
+  {
+    .directive = ".seh_save_freg_x", .offset_bits = 5, .reg_bits = 3,
+    .code_bits = 8, .code = AARCH64_UNOP_SAVEFREGX, .offset_right_shift = 3,
+    .offset = 1, .reg_right_shift = 0, .reg_offset = 8, .size = 2
+  },
+  {
+    .directive = ".seh_save_lrpair", .offset_bits = 6, .reg_bits = 3,
+    .code_bits = 7, .code = AARCH64_UNOP_SAVELRPAIR, .offset_right_shift = 3,
+    .offset = 0, .reg_right_shift = 1, .reg_offset = 19, .size = 2
+  },
+  {
+    .directive = ".seh_save_fplr", .offset_bits = 6, .reg_bits = 0,
+    .code_bits = 2, .code = AARCH64_UNOP_SAVEFPLR, .offset_right_shift = 3,
+    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
+  },
+  {
+    .directive = ".seh_save_fplr_x", .offset_bits = 6, .reg_bits = 0,
+    .code_bits = 2, .code = AARCH64_UNOP_SAVEFPLRX, .offset_right_shift = 3,
+    .offset = 1, .reg_right_shift = 0, .reg_offset = 0, .size = 1
+  },
+  {
+    .directive = ".seh_save_r19r20_x", .offset_bits = 5, .reg_bits = 0,
+    .code_bits = 3, .code = AARCH64_UNOP_SAVER19R20X, .offset_right_shift = 3,
+    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
+  },
+  {
+    .directive = ".seh_add_fp",	.offset_bits = 8, .reg_bits = 0,
+    .code_bits = 8, .code = AARCH64_UNOP_ADDFP,	.offset_right_shift = 0,
+    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 2
+  },
+  {
+    .directive = ".seh_set_fp",	.offset_bits = 0, .reg_bits = 0,
+    .code_bits = 8, .code = AARCH64_UNOP_SETFP,	.offset_right_shift = 0,
+    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
+  },
+  {
+    .directive = ".seh_save_next", .offset_bits = 0, .reg_bits = 0,
+    .code_bits = 8, .code = AARCH64_UNOP_SAVENEXT, .offset_right_shift = 0,
+    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
+  },
+  {
+    .directive = ".seh_nop", .offset_bits = 0, .reg_bits = 0,
+    .code_bits = 8, .code = AARCH64_UNOP_NOP, .offset_right_shift = 0,
+    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
+  },
+  {
+    .directive = ".seh_pac_sign_lr", .offset_bits = 0, .reg_bits = 0,
+    .code_bits = 8, .code = AARCH64_UNOP_PACSIGNLR, .offset_right_shift = 0,
+    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
+  },
+  {
+    .directive = NULL, .offset_bits = 0, .reg_bits = 0,
+    .code_bits = 8, .code = AARCH64_UNOP_END, .offset_right_shift = 0,
+    .offset = 0, .reg_right_shift = 0, .reg_offset = 0, .size = 1
+  },
+};
+
+/* Set for current context the default handler.  */
+static void
+obj_coff_seh_handler (const int what ATTRIBUTE_UNUSED)
+{
+  char *symbol_name;
+  char name_end;
+
+  if (!verify_context (".seh_handler"))
+    return;
+
+  if (*input_line_pointer == 0 || *input_line_pointer == '\n')
+    as_bad (_(".seh_handler requires a handler"));
+
+  SKIP_WHITESPACE ();
+
+  if (*input_line_pointer == '@')
+    {
+      name_end = get_symbol_name (&symbol_name);
+
+      seh_ctx_cur->handler.X_op = O_constant;
+      seh_ctx_cur->handler.X_add_number = 0;
+
+      if (strcasecmp (symbol_name, "@0") == 0
+	  || strcasecmp (symbol_name, "@null") == 0)
+	;
+      else if (strcasecmp (symbol_name, "@1") == 0)
+	seh_ctx_cur->handler.X_add_number = 1;
+      else
+	as_bad (_("unknown constant value '%s' for handler"), symbol_name);
+
+      (void) restore_line_pointer (name_end);
+    }
+  else
+    expression (&seh_ctx_cur->handler);
+
+  seh_ctx_cur->handler_data.X_op = O_constant;
+  seh_ctx_cur->handler_data.X_add_number = 0;
+  seh_ctx_cur->xdata_header.x = 1;
+
+  while (skip_whitespace_and_comma (0))
+    {
+      name_end = get_symbol_name (&symbol_name);
+      (void) restore_line_pointer (name_end);
+    }
+}
+
+/* Switch to subsection for handler data for exception region.  */
+static void
+obj_coff_seh_handlerdata (const int what ATTRIBUTE_UNUSED)
+{
+  demand_empty_rest_of_line ();
+
+  switch_xdata (seh_ctx_cur->subsection + 1, seh_ctx_cur->code_seg);
+}
+
+/* Obtain available unwind element.  */
+static void
+seh_aarch64_add_unwind_element (const seh_aarch64_unwind_types unwind_type,
+				unsigned offset, unsigned reg)
+{
+  gas_assert (in_seh_proc);
+
+  const struct aarch64_unwind_code_pack_info *unwind_code_pack_info
+    = aarch64_unwind_code_pack_data + unwind_type;
+  unsigned value_offset_bits = 0;
+
+  if ((seh_ctx_cur->unwind_codes_byte_count
+      + unwind_code_pack_info->size) > AARCH64_MAX_UNWIND_CODES_SIZE)
+    as_bad (_("no unwind element available."));
+
+  seh_aarch64_unwind_code *aarch64_element;
+  aarch64_element = seh_ctx_cur->unwind_codes
+		    + seh_ctx_cur->unwind_codes_count++;
+  aarch64_element->value = 0;
+
+  if (unwind_code_pack_info->offset_bits)
+    {
+      offset = (offset >> unwind_code_pack_info->offset_right_shift)
+	       - unwind_code_pack_info->offset;
+      offset &= (1 << unwind_code_pack_info->offset_bits) - 1;
+      aarch64_element->value |= offset << value_offset_bits;
+      value_offset_bits += unwind_code_pack_info->offset_bits;
+    }
+
+  if (unwind_code_pack_info->reg_bits)
+    {
+      reg = (reg >> unwind_code_pack_info->reg_right_shift)
+	    - unwind_code_pack_info->reg_offset;
+      reg &= (1 << unwind_code_pack_info->reg_bits) - 1;
+      aarch64_element->value |= reg << value_offset_bits;
+      value_offset_bits += unwind_code_pack_info->reg_bits;
+    }
+
+  if (unwind_code_pack_info->code_bits)
+    {
+      unsigned code = unwind_code_pack_info->code;
+      code &= (1 << unwind_code_pack_info->code_bits) - 1;
+      aarch64_element->value |= code << value_offset_bits;
+    }
+
+  aarch64_element->type = unwind_type;
+  seh_ctx_cur->unwind_codes_byte_count += unwind_code_pack_info->size;
+}
+
+/* Mark begin of new context.  */
+static void
+obj_coff_seh_proc (const int what ATTRIBUTE_UNUSED)
+{
+  char *symbol_name;
+  char name_end;
+
+  if (in_seh_proc)
+    as_bad (_("previous SEH entry not closed (missing .seh_endproc)"));
+
+  if (*input_line_pointer == 0 || *input_line_pointer == '\n')
+    as_bad (_(".seh_proc requires function label name"));
+
+
+  if (!seh_ctx_root)
+  {
+    seh_ctx_root = XCNEW (seh_context);
+    seh_ctx_cur = seh_ctx_root;
+  }
+  else
+  {
+    seh_ctx_cur->next = XCNEW (seh_context);
+    seh_ctx_cur = seh_ctx_cur->next;
+  }
+
+  seh_ctx_cur->next = NULL;
+  seh_ctx_cur->code_seg = now_seg;
+
+  /* The current implementation always use a pair of .pdata and .xdata
+     records.  */
+  const bool use_xdata = true;
+
+  if (use_xdata)
+    {
+      x_segcur = seh_hash_find_or_make (seh_ctx_cur->code_seg, ".xdata");
+      seh_ctx_cur->subsection = x_segcur->subseg;
+      x_segcur->subseg += 2;
+
+      /* Initialize an empty .xdata record.  */
+      seh_ctx_cur->unwind_codes_count = 0;
+      seh_ctx_cur->unwind_codes_byte_count = 0;
+      seh_ctx_cur->epilogue_scopes_count = 0;
+      seh_ctx_cur->epilogue_scopes_capacity = 0;
+      seh_ctx_cur->epilogue_scopes = NULL;
+      seh_ctx_cur->xdata_header.x = 0;
+    }
+
+  SKIP_WHITESPACE ();
+
+  name_end = get_symbol_name (&symbol_name);
+  seh_ctx_cur->func_name = xstrdup (symbol_name);
+  (void) restore_line_pointer (name_end);
+
+  demand_empty_rest_of_line ();
+
+  seh_ctx_cur->start_addr = symbol_temp_new_now ();
+  in_seh_proc = true;
+}
+
+/* Mark end of prologue for current context.  */
+static void
+obj_coff_seh_endprologue (const int what ATTRIBUTE_UNUSED)
+{
+  if (!verify_context (".seh_endprologue")
+      || !seh_validate_seg (".seh_endprologue"))
+    return;
+  demand_empty_rest_of_line ();
+
+  if (seh_ctx_cur->endprologue_addr != NULL)
+    as_warn (_("duplicate .seh_endprologue in .seh_proc block"));
+  else
+    seh_ctx_cur->endprologue_addr = symbol_temp_new_now ();
+
+  /* Unwind codes need to be reversed.  */
+  for (unsigned i = 0, n = seh_ctx_cur->unwind_codes_count; i < n / 2; ++i)
+    {
+      seh_aarch64_unwind_code *unwind_codes = seh_ctx_cur->unwind_codes;
+      const seh_aarch64_unwind_code temp = unwind_codes[i];
+      unwind_codes[i] = unwind_codes[n-i-1];
+      unwind_codes[n-i-1] = temp;
+    }
+
+   seh_aarch64_add_unwind_element (unwind_end, 0, 0);
+}
+
+/* Mark end of current context.  */
+static void
+obj_coff_seh_endproc (const int what ATTRIBUTE_UNUSED)
+{
+  demand_empty_rest_of_line ();
+  if (!in_seh_proc)
+    as_bad (_(".seh_endproc used without .seh_proc"));
+
+  seh_validate_seg (".seh_endproc");
+
+  seh_ctx_cur->end_addr = symbol_temp_new_now ();
+  in_seh_proc = false;
+}
+
+static void
+obj_coff_seh_startepilogue (const int what ATTRIBUTE_UNUSED)
+{
+  if (!verify_context (".seh_startepilogue")
+      || !seh_validate_seg (".seh_startepilogue"))
+    return;
+  demand_empty_rest_of_line ();
+
+  const unsigned max_epilogue_scopes = AARCH64_MAX_EPILOGUE_SCOPES;
+  if (seh_ctx_cur->epilogue_scopes_count >= max_epilogue_scopes)
+    as_bad (_("no epilogue scopes available."));
+
+  symbolS *epilogue_start_addr = symbol_temp_new_now ();
+  expressionS exp;
+  exp.X_op = O_subtract;
+  exp.X_add_symbol = epilogue_start_addr;
+  exp.X_op_symbol = seh_ctx_cur->start_addr;
+  exp.X_add_number = 0;
+
+  if (!resolve_expression (&exp) || exp.X_op != O_constant
+      || exp.X_add_number < 0)
+    as_bad (_(".seh_startepilog offset expression for %s "
+	    "does not evaluate to a non-negative constant"),
+	    S_GET_NAME (epilogue_start_addr));
+
+  if (seh_ctx_cur->epilogue_scopes_count
+      >= seh_ctx_cur->epilogue_scopes_capacity)
+    {
+      const unsigned initial_capacity = 32;
+      if (seh_ctx_cur->epilogue_scopes_capacity)
+	seh_ctx_cur->epilogue_scopes_capacity *= 2;
+      else
+	seh_ctx_cur->epilogue_scopes_capacity = initial_capacity;
+
+      seh_ctx_cur->epilogue_scopes
+	= XRESIZEVEC (seh_aarch64_epilogue_scope, seh_ctx_cur->epilogue_scopes,
+		     seh_ctx_cur->epilogue_scopes_capacity);
+    }
+
+  seh_aarch64_epilogue_scope *epilogue_scope = seh_ctx_cur->epilogue_scopes
+    + seh_ctx_cur->epilogue_scopes_count;
+  epilogue_scope->epilogue_start_offset = exp.X_add_number / 4;
+  epilogue_scope->reserved = 0;
+  epilogue_scope->epilogue_start_index = seh_ctx_cur->unwind_codes_byte_count;
+  seh_ctx_cur->epilogue_scopes_count++;
+}
+
+static void
+obj_coff_seh_endepilogue (const int what ATTRIBUTE_UNUSED)
+{
+  if (!verify_context (".seh_endepilogue")
+      || !seh_validate_seg (".seh_endepilogue"))
+    return;
+
+  demand_empty_rest_of_line ();
+
+  expressionS exp;
+  symbolS *epilogue_end_addr = symbol_temp_new_now ();
+  exp.X_op = O_subtract;
+  exp.X_add_symbol = epilogue_end_addr;
+  exp.X_op_symbol = seh_ctx_cur->start_addr;
+  exp.X_add_number = 0;
+
+  if (!resolve_expression (&exp) || exp.X_op != O_constant
+      || exp.X_add_number < 0)
+    as_bad (_(".seh_endepilogue offset expression for %s "
+	    "does not evaluate to a non-negative constant"),
+	    S_GET_NAME (epilogue_end_addr));
+
+   seh_aarch64_epilogue_scope *epilogue_scope = seh_ctx_cur->epilogue_scopes
+     + seh_ctx_cur->epilogue_scopes_count - 1;
+
+   epilogue_scope->epilogue_end_offset = exp.X_add_number;
+
+  /* End code.  */
+  seh_aarch64_add_unwind_element (unwind_end, 0, 0);
+}
+
+/* End-of-file hook.  */
+static void
+free_seh_ctx (struct seh_aarch64_context *seh_ctx)
+{
+  free (seh_ctx->func_name);
+  const seh_aarch64_func_fragment *fragment = seh_ctx->func_fragment.next;
+  while (fragment)
+    {
+      const seh_aarch64_func_fragment *next = fragment->next;
+      XDELETE (fragment);
+      fragment = next;
+    }
+  XDELETEVEC (seh_ctx->epilogue_scopes);
+  free (seh_ctx);
+}
+
+static void
+obj_coff_seh_save_reg (const int type)
+{
+  gas_assert (type >= 0 && type <= unwind_last_type);
+
+  const struct aarch64_unwind_code_pack_info *unwind_code_pack_info
+    = aarch64_unwind_code_pack_data + type;
+
+  if (!unwind_code_pack_info->directive
+      || !seh_validate_seg (unwind_code_pack_info->directive))
+    return;
+
+  SKIP_WHITESPACE ();
+
+  char *symbol_name = NULL;
+  unsigned reg = -1;
+
+  if (unwind_code_pack_info->reg_bits)
+    {
+      char name_end = get_symbol_name (&symbol_name);
+      reg = atoi (symbol_name + 1);
+      (void) restore_line_pointer (name_end);
+
+      if (!skip_whitespace_and_comma (1))
+	return;
+
+      if (reg > 30)
+	as_bad (_("register number is out of range"));
+    }
+
+  offsetT off = -1;
+  if (unwind_code_pack_info->offset_bits)
+    {
+      off = get_absolute_expression ();
+
+      if (off < 0)
+	as_bad (_("offset is negative"));
+    }
+
+  demand_empty_rest_of_line ();
+
+  seh_aarch64_add_unwind_element (type, off, reg);
+}
+
+/* Add a stack-allocation token to current context.  */
+static void
+obj_coff_seh_stackalloc (const int what ATTRIBUTE_UNUSED)
+{
+  const offsetT off = get_absolute_expression ();
+  demand_empty_rest_of_line ();
+
+  /* aarch64 offset should be encoded in multiples of sixteen.  */
+  if ((off & 0xf) != 0)
+    as_bad (_(".seh_stackalloc offset < 16-byte stack alignment"));
+
+  if (off < 0x200)
+    seh_aarch64_add_unwind_element (unwind_alloc_s, off, 0);
+  else if (off < 0x8000)
+    seh_aarch64_add_unwind_element (unwind_alloc_m, off, 0);
+  else if (off < 0x10000000)
+    seh_aarch64_add_unwind_element (unwind_alloc_l, off, 0);
+  else
+    as_bad (_(".seh_stackalloc offset out of range"));
+}
+
+/* Data writing routines.  */
+static void
+seh_aarch64_emit_epilog_scopes (const seh_context *seh_ctx,
+				const uint64_t fragment_offset,
+				const unsigned prolog_size,
+				const unsigned first_fragment_scope,
+				const unsigned last_fragment_scope,
+				const bool has_phantom_prolog)
+{
+  unsigned start_index_offset = 0;
+  const seh_aarch64_epilogue_scope *scopes = seh_ctx->epilogue_scopes;
+  if (first_fragment_scope < seh_ctx->epilogue_scopes_count)
+    start_index_offset = scopes[first_fragment_scope].epilogue_start_index
+			 - prolog_size;
+  if (has_phantom_prolog)
+    {
+      if (start_index_offset == 0)
+	as_bad (_("start index offset for the epilogue cannot be 0 when "
+		"phantom prolog is used"));
+      --start_index_offset;
+    }
+
+  for (unsigned i = first_fragment_scope; i < last_fragment_scope; ++i)
+    {
+      seh_aarch64_epilogue_scope scope = seh_ctx->epilogue_scopes[i];
+      scope.epilogue_start_offset_reduced = (scope.epilogue_start_offset
+					    - fragment_offset) >> 2;
+      scope.epilogue_start_index -= start_index_offset;
+      uint32_t scope_code;
+      memcpy (&scope_code, &scope, sizeof (scope_code));
+      md_number_to_chars (frag_more (4), scope_code, 4);
+    }
+}
+
+static void
+seh_aarch64_emit_unwind_codes (const seh_context *seh_ctx,
+			       const unsigned prolog_size,
+			       const unsigned first_epilog_index,
+			       const unsigned last_epilog_index,
+			       const bool has_phantom_prolog)
+{
+  unsigned total_byte_count = 0;
+
+  if (has_phantom_prolog)
+    {
+      ++total_byte_count;
+      md_number_to_chars (frag_more (1), AARCH64_UNOP_ENDC, 1);
+    }
+
+  unsigned unwind_bytes_offset = 0;
+  for (unsigned i = 0; i < seh_ctx->unwind_codes_count; ++i)
+    {
+      const seh_aarch64_unwind_code *code = seh_ctx->unwind_codes
+					    + i;
+      const unsigned byte_count
+	= aarch64_unwind_code_pack_data[code->type].size;
+      unwind_bytes_offset += byte_count;
+
+      if (unwind_bytes_offset > last_epilog_index)
+	break;
+
+      if (unwind_bytes_offset > prolog_size
+	  && unwind_bytes_offset <= first_epilog_index)
+	continue;
+
+      /*  emit unwind code bytes in big endian.  */
+      number_to_chars_bigendian (frag_more (byte_count), code->value,
+				 byte_count);
+      total_byte_count += byte_count;
+    }
+
+    /* handle word alignment.  */
+    unsigned required_padding = (4 - total_byte_count % 4) % 4;
+    if (required_padding)
+      {
+	/* Use AARCH64_UNOP_NOP for alignment.  */
+	const uint32_t nop_chain = (AARCH64_UNOP_NOP << 24)
+				   | (AARCH64_UNOP_NOP << 16)
+				   | (AARCH64_UNOP_NOP << 8)
+				   | AARCH64_UNOP_NOP;
+
+	md_number_to_chars (frag_more (required_padding), nop_chain,
+			    required_padding);
+      }
+}
+
+static bool
+seh_function_size (const struct seh_aarch64_context *seh_ctx,
+		  uintptr_t *size)
+{
+  fragS *start_frag, *end_frag;
+  addressT start_offset, end_offset;
+  start_frag = symbol_get_frag_and_value (seh_ctx->start_addr, &start_offset);
+  end_frag = symbol_get_frag_and_value (seh_ctx->end_addr, &end_offset);
+
+  intptr_t func_size = end_frag->fr_address + end_offset
+		       - start_frag->fr_address - start_offset;
+  if (func_size < 0)
+    return false;
+
+  *size = func_size;
+  return true;
+}
+
+/* Write out the xdata information for one function.  */
+static void
+seh_aarch64_write_function_xdata (struct seh_aarch64_context *seh_ctx)
+{
+  if (!seh_ctx->unwind_codes_byte_count)
+    return;
+
+  const segT save_seg = now_seg;
+  const subsegT save_subseg = now_subseg;
+
+  switch_xdata (seh_ctx->subsection, seh_ctx->code_seg);
+
+  /* Set 4-byte alignment.  */
+  frag_align (2, 0, 0);
+
+  uintptr_t func_size = 0;
+  if (!seh_function_size (seh_ctx, &func_size))
+    {
+      as_bad (_("the function size for %s has not been evaluated"),
+	      seh_ctx->func_name);
+      return;
+    }
+
+  /* The large functions should be split into fragments smaller than 1MB with
+     4 bytes alignment.
+     https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170#large-functions.  */
+  const unsigned max_frag_size = (1 << 20) - 4;
+  const bool is_fragmented_function = func_size > max_frag_size;
+
+  /* [first_fragment_scope, last_fragment_scope).  */
+  unsigned prolog_insn_count = 0;
+  for (unsigned i = 0; i < seh_ctx->unwind_codes_count; ++i)
+    {
+      if (seh_ctx->unwind_codes[i].type == unwind_end)
+	{
+	  prolog_insn_count = i + 1;
+	  break;
+	}
+    }
+
+  unsigned prolog_size;
+  if (seh_ctx->epilogue_scopes_count)
+    prolog_size = seh_ctx->epilogue_scopes[0].epilogue_start_index;
+  else
+    prolog_size = seh_ctx->unwind_codes_byte_count;
+
+  seh_aarch64_func_fragment *fragment;
+  fragment = &seh_ctx->func_fragment;
+  uintptr_t fragment_offset = 0;
+  unsigned first_fragment_scope = 0;
+  unsigned last_fragment_scope = 0;
+  while (true)
+    {
+      fragment->xdata_addr = symbol_temp_new_now ();
+      fragment->offset = fragment_offset;
+      fragment->next = NULL;
+
+      uintptr_t frag_size = func_size - fragment_offset;
+      if (frag_size > max_frag_size)
+	frag_size = max_frag_size;
+
+      const bool is_first_frag = fragment_offset == 0;
+      const bool is_last_frag = (fragment_offset + frag_size) == func_size;
+
+      if (!is_fragmented_function)
+	last_fragment_scope = seh_ctx->epilogue_scopes_count;
+      else
+	{
+	  first_fragment_scope = last_fragment_scope;
+	  for (unsigned i = first_fragment_scope;
+	       i < seh_ctx->epilogue_scopes_count; ++i)
+	    {
+	      const seh_aarch64_epilogue_scope *scope
+		= seh_ctx->epilogue_scopes;
+	      scope += i;
+	      if (scope->epilogue_start_offset >= (fragment_offset + frag_size))
+		break;
+
+	      if (scope->epilogue_end_offset >= (fragment_offset + frag_size))
+		{
+		  frag_size = scope->epilogue_start_offset - fragment_offset;
+		  break;
+		}
+
+	      if (scope->epilogue_start_offset >= fragment_offset)
+		last_fragment_scope = i + 1;
+	    }
+	}
+
+      seh_aarch64_xdata_header *header = &seh_ctx->xdata_header;
+      const seh_aarch64_epilogue_scope *scopes = seh_ctx->epilogue_scopes;
+
+      const uint32_t func_length_encoded = frag_size >> 2;
+      header->func_length = func_length_encoded;
+      header->vers = 0;
+      header->e = 0;
+      header->code_words = 0;
+      header->epilogue_count = 0;
+
+      header->ext_code_words = 0;
+      header->ext_epilogue_count = last_fragment_scope
+				   - first_fragment_scope;
+      header->reserved = 0;
+
+      unsigned first_epilog_index = 0;
+      unsigned last_epilog_index = 0;
+      if (!header->ext_epilogue_count)
+	{
+	  first_epilog_index = prolog_size;
+	  last_epilog_index = prolog_size;
+	}
+      else
+	{
+	  const seh_aarch64_epilogue_scope *scope;
+	  scope = scopes + first_fragment_scope;
+	  first_epilog_index = scope->epilogue_start_index;
+	  if (last_fragment_scope == seh_ctx->epilogue_scopes_count)
+	    last_epilog_index = seh_ctx->unwind_codes_byte_count;
+	  else
+	    {
+	      scope = scopes + last_fragment_scope;
+	      last_epilog_index = scope->epilogue_start_index;
+	    }
+	}
+
+      unsigned unwind_bytes = 0;
+      if (is_first_frag || is_last_frag)
+	unwind_bytes += prolog_size;
+
+      if (header->ext_epilogue_count)
+	unwind_bytes += last_epilog_index - first_epilog_index;
+
+      const bool has_phantom_prolog = is_fragmented_function && is_last_frag;
+      if (has_phantom_prolog && unwind_bytes)
+	{
+	  /* One more epilogue scope and unwind code are emitted with phantom
+	     prolog.  */
+	  unwind_bytes += 1;
+	  ++header->ext_epilogue_count;
+	}
+
+      /* Calculate the number of code words with 4-byte alignment.  */
+      header->ext_code_words = (unwind_bytes + 3) / 4;
+
+      if ((header->ext_code_words == 0 && header->ext_epilogue_count == 0)
+	  || header->ext_code_words > 31
+	  || header->ext_epilogue_count > 31)
+	md_number_to_chars (frag_more (8),
+			   seh_ctx->xdata_header_value, 8);
+      else
+	{
+	  header->code_words = header->ext_code_words;
+	  header->epilogue_count = header->ext_epilogue_count;
+	  if (header->epilogue_count == 1)
+	    {
+	      header->e = 1;
+	      if (has_phantom_prolog)
+		header->ext_epilogue_count = 0;
+	      else
+		{
+		  const seh_aarch64_epilogue_scope *scope;
+		  scope = scopes + first_fragment_scope;
+		  header->ext_epilogue_count = scope->epilogue_start_index;
+		}
+	    }
+	  md_number_to_chars (frag_more (4),
+			      seh_ctx->xdata_header_value, 4);
+	}
+
+      if (header->ext_epilogue_count && !header->e)
+	{
+	  seh_aarch64_emit_epilog_scopes (seh_ctx,
+					 fragment_offset, prolog_size,
+					 first_fragment_scope,
+					 last_fragment_scope,
+					 has_phantom_prolog);
+	  if (has_phantom_prolog)
+	    {
+	      const uint32_t epilog_start_index_encoded = 1 << 22;
+	      const uint32_t epilog_start_offset_encoded
+		= (frag_size - prolog_insn_count * 4) >> 2;
+	      md_number_to_chars (frag_more (4),
+				  epilog_start_index_encoded
+				  | epilog_start_offset_encoded, 4);
+	    }
+	}
+
+      if (header->ext_code_words)
+	seh_aarch64_emit_unwind_codes (seh_ctx, prolog_size, first_epilog_index,
+				       last_epilog_index, has_phantom_prolog);
+
+      if (header->x == 1)
+	{
+	  if (seh_ctx->handler.X_op == O_symbol)
+	    seh_ctx->handler.X_op = O_symbol_rva;
+
+	  emit_expr (&seh_ctx->handler, 4);
+	}
+
+      fragment_offset += frag_size;
+      if (fragment_offset == func_size)
+	break;
+
+      fragment->next = XCNEW (seh_aarch64_func_fragment);
+      fragment = fragment->next;
+    }
+
+  subseg_set (save_seg, save_subseg);
+}
+
+/* Write out pdata for one function.  */
+static void
+seh_aarch64_write_function_pdata (const seh_context *seh_ctx)
+{
+  expressionS exp;
+  const segT save_seg = now_seg;
+  const subsegT save_subseg = now_subseg;
+  memset (&exp, 0, sizeof (expressionS));
+  switch_pdata (seh_ctx->code_seg);
+
+  if (seh_ctx->unwind_codes_byte_count)
+    {
+      const seh_aarch64_func_fragment *fragment = &seh_ctx->func_fragment;
+      while (fragment)
+	{
+	  exp.X_op = O_symbol_rva;
+	  exp.X_add_number = fragment->offset;
+	  exp.X_add_symbol = seh_ctx->start_addr;
+	  emit_expr (&exp, 4);
+
+	  exp.X_op = O_symbol_rva;
+	  /* TODO: Implementing packed unwind data.  */
+	  exp.X_add_number = 0;
+	  exp.X_add_symbol = fragment->xdata_addr;
+	  emit_expr (&exp, 4);
+	  fragment = fragment->next;
+	}
+    }
+
+  subseg_set (save_seg, save_subseg);
+}
+
+void
+seh_aarch64_write_data (void)
+{
+  if (in_seh_proc)
+    as_bad (_("open SEH entry at end of file (missing .seh_endproc)"));
+
+  if (!seh_ctx_root)
+    return;
+
+  struct seh_aarch64_context *seh_ctx = seh_ctx_root;
+  seh_ctx_root = NULL;
+
+  /* Relax the segment to be able to calculate the function sizes.  */
+  subsegs_finish_section (seh_ctx->code_seg);
+  const segment_info_type *seginfo = seg_info (seh_ctx->code_seg);
+  relax_segment (seginfo->frchainP->frch_root, seh_ctx->code_seg, 0);
+
+  while (seh_ctx)
+  {
+    seh_aarch64_write_function_xdata (seh_ctx);
+    seh_aarch64_write_function_pdata (seh_ctx);
+    struct seh_aarch64_context *next = seh_ctx->next;
+    free_seh_ctx (seh_ctx);
+    seh_ctx = next;
+  }
+}
+
+void
+obj_coff_seh_do_final (void)
+{
+}
diff --git a/gas/config/obj-coff-seh-aarch64.h b/gas/config/obj-coff-seh-aarch64.h
new file mode 100644
index 00000000000..bda7e00adc5
--- /dev/null
+++ b/gas/config/obj-coff-seh-aarch64.h
@@ -0,0 +1,265 @@ 
+/* SEH .pdata/.xdata COFF object file format on AArch64
+   Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This file is part of GAS.
+
+   GAS is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   GAS is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with GAS; see the file COPYING.  If not, write to the Free
+   Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
+   02110-1301, USA.  */
+
+/* SEH COFF AArch64 implementation partially intersects with the x64
+   version, however it has a different extension to the unwind codes.
+   It emits SEH data to pdata and xdata sections.  In some cases SEH
+   data could be emitted to a packed record in the pdata section
+   without the need for data in the xdata section.  However, the packed
+   pdata record is not implemented yet.  */
+
+#ifndef OBJ_COFF_SEH_AARCH64_H
+#define OBJ_COFF_SEH_AARCH64_H
+
+typedef enum seh_aarch64_unwind_types
+{
+  unwind_alloc_s,
+  unwind_alloc_m,
+  unwind_alloc_l,
+  unwind_save_reg,
+  unwind_save_reg_x,
+  unwind_save_regp,
+  unwind_save_regp_x,
+  unwind_save_fregp,
+  unwind_save_fregp_x,
+  unwind_save_freg,
+  unwind_save_freg_x,
+  unwind_save_lrpair,
+  unwind_save_fplr,
+  unwind_save_fplr_x,
+  unwind_save_r19r20_x,
+  unwind_add_fp,
+  unwind_set_fp,
+  unwind_save_next,
+  unwind_nop,
+  unwind_pac_sign_lr,
+  unwind_end,
+  unwind_end_c,
+  unwind_last_type = unwind_end_c
+} seh_aarch64_unwind_types;
+
+#define SEH_CMDS						      \
+  /* Start a function that contains SEH.  */			      \
+  {"seh_proc", obj_coff_seh_proc, 0},				      \
+								      \
+  /* End a function that contains SEH.  */			      \
+  {"seh_endproc", obj_coff_seh_endproc, 0},			      \
+								      \
+  /* End a SEH prolog with unwinding codes.  */			      \
+  {"seh_endprologue", obj_coff_seh_endprologue, 0},		      \
+								      \
+  /* Allocate stack.  */					      \
+  {"seh_stackalloc", obj_coff_seh_stackalloc, 0},		      \
+								      \
+  /* Set a SEH handler.  */					      \
+  {"seh_handler", obj_coff_seh_handler, 0},			      \
+								      \
+  /* Set a SEH handler data.  */				      \
+  {"seh_handlerdata", obj_coff_seh_handlerdata, 0},		      \
+								      \
+  /* Start a SEH epilogue.  */					      \
+  {"seh_startepilogue", obj_coff_seh_startepilogue, 0},		      \
+								      \
+  /* End a SEH epilogue.  */					      \
+  {"seh_endepilogue", obj_coff_seh_endepilogue, 0},		      \
+								      \
+  /* Save an 'x' register.  */					      \
+  {"seh_save_reg", obj_coff_seh_save_reg, unwind_save_reg},	      \
+								      \
+  /* Save an 'x' register with a pre-indexed offset.  */	      \
+  {"seh_save_reg_x", obj_coff_seh_save_reg, unwind_save_reg_x},	      \
+								      \
+  /* Save an 'x' register pair.  */				      \
+  {"seh_save_regp", obj_coff_seh_save_reg, unwind_save_regp},	      \
+								      \
+  /* Save an 'x' register pair with a pre-indexed offset.  */	      \
+  {"seh_save_regp_x", obj_coff_seh_save_reg, unwind_save_regp_x},     \
+								      \
+  /* Save an 'x' register and lr.  */				      \
+  {"seh_save_lrpair", obj_coff_seh_save_reg, unwind_save_lrpair},     \
+								      \
+  /* Save a 'd' register pair.  */				      \
+  {"seh_save_fregp", obj_coff_seh_save_reg, unwind_save_fregp},	      \
+								      \
+  /* Save a 'd' register pair with a pre-indexed offset.  */	      \
+  {"seh_save_fregp_x", obj_coff_seh_save_reg, unwind_save_fregp_x},   \
+								      \
+  /* Save a 'd' register.  */					      \
+  {"seh_save_freg", obj_coff_seh_save_reg, unwind_save_freg},	      \
+								      \
+  /* Save a 'd' register with a pre-indexed offset.  */		      \
+  {"seh_save_freg_x", obj_coff_seh_save_reg, unwind_save_freg_x},     \
+								      \
+  /* Save fp and lr registers.  */				      \
+  {"seh_save_fplr", obj_coff_seh_save_reg, unwind_save_fplr},	      \
+								      \
+  /* Save fp and lr registers with a pre-indexed offset.  */	      \
+  {"seh_save_fplr_x", obj_coff_seh_save_reg, unwind_save_fplr_x},     \
+								      \
+  /* Save x19 and x20 registers with a pre-indexed offset.  */	      \
+  {"seh_save_r19r20_x", obj_coff_seh_save_reg, unwind_save_r19r20_x}, \
+								      \
+  /* Set fp by sp + offset.  */					      \
+  {"seh_add_fp", obj_coff_seh_save_reg, unwind_add_fp},		      \
+								      \
+  /* Unwind operation is not required.  */			      \
+  {"seh_nop", obj_coff_seh_save_reg, unwind_nop},		      \
+								      \
+  /* Sign the return address in lr with pacibsp.  */		      \
+  {"seh_pac_sign_lr", obj_coff_seh_save_reg, unwind_pac_sign_lr},     \
+								      \
+  /* Set fp by sp.  */						      \
+  {"seh_set_fp", obj_coff_seh_save_reg, unwind_set_fp},		      \
+								      \
+  /* Save next register pair.  */				      \
+  {"seh_save_next", obj_coff_seh_save_reg, unwind_save_next},
+
+/* AArch64 exceptions handling and unwinding structures.
+   https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling#pdata-records.  */
+
+typedef struct seh_aarch64_unwind_code
+{
+  unsigned value;
+  seh_aarch64_unwind_types type;
+} seh_aarch64_unwind_code;
+
+typedef struct seh_aarch64_packed_unwind_data
+{
+  uint32_t flag : 2;
+  uint32_t func_length : 11;
+  uint32_t frame_size : 9;
+  uint32_t cr : 2;
+  uint32_t h : 1;
+  uint32_t regI : 4;
+  uint32_t regF : 3;
+} seh_aarch64_packed_unwind_data;
+
+typedef struct seh_aarch64_except_info
+{
+  uint32_t flag : 2;
+  uint32_t except_info_rva : 30;
+} seh_aarch64_except_info;
+
+typedef union seh_aarch64_unwind_info
+{
+  seh_aarch64_except_info except_info;
+  seh_aarch64_packed_unwind_data packed_unwind_data;
+} seh_aarch64_unwind_info;
+
+typedef struct seh_aarch64_xdata_header
+{
+  uint32_t func_length : 18;
+  uint32_t vers : 2;
+  uint32_t x : 1;
+  uint32_t e : 1;
+  uint32_t epilogue_count : 5;
+  uint32_t code_words : 5;
+  uint32_t ext_epilogue_count : 16;
+  uint32_t ext_code_words : 8;
+  uint32_t reserved : 8;
+} seh_aarch64_xdata_header;
+
+typedef struct seh_aarch64_epilogue_scope
+{
+  uint32_t epilogue_start_offset_reduced : 18;
+  uint32_t reserved : 4;
+  uint32_t epilogue_start_index : 10;
+  bfd_vma epilogue_start_offset;
+  bfd_vma epilogue_end_offset;
+} seh_aarch64_epilogue_scope;
+
+typedef struct seh_aarch64_func_fragment
+{
+  bfd_vma offset;
+  symbolS *xdata_addr;
+  struct seh_aarch64_func_fragment *next;
+} seh_aarch64_func_fragment;
+
+/* AARCH64_MAX_UNWIND_CODES is limited by
+   seh_aarch64_xdata_header::ext_code_words.  */
+#define AARCH64_MAX_UNWIND_CODES (255 * 4)
+#define AARCH64_MAX_UNWIND_CODES_SIZE (255 * 4)
+/* AARCH64_MAX_EPILOGUE_SCOPES is limited by
+   seh_aarch64_xdata_header::ext_epilogue_count.  */
+#define AARCH64_MAX_EPILOGUE_SCOPES 65535
+
+typedef struct seh_aarch64_context
+{
+  struct seh_aarch64_context *next;
+
+  /* Initial code-segment.  */
+  segT code_seg;
+  /* Function name.  */
+  char *func_name;
+  /* BeginAddress.  */
+  symbolS *start_addr;
+  /* EndAddress.  */
+  symbolS *end_addr;
+  /* PrologueEnd.  */
+  symbolS *endprologue_addr;
+  /* ExceptionHandler.  */
+  expressionS handler;
+  /* ExceptionHandlerData.  */
+  expressionS handler_data;
+
+  subsegT subsection;
+
+  union {
+    seh_aarch64_xdata_header xdata_header;
+    valueT xdata_header_value;
+  };
+  unsigned unwind_codes_count;
+  unsigned unwind_codes_byte_count;
+  seh_aarch64_unwind_code unwind_codes[AARCH64_MAX_UNWIND_CODES];
+  unsigned epilogue_scopes_count;
+  unsigned epilogue_scopes_capacity;
+  seh_aarch64_epilogue_scope *epilogue_scopes;
+  expressionS except_handler;
+  expressionS except_handler_data;
+  /* The function fragments.  */
+  seh_aarch64_func_fragment func_fragment;
+} seh_context;
+
+/* aarch64 unwind code prefixes.  */
+
+#define AARCH64_UNOP_ALLOCS	  0b000U
+#define AARCH64_UNOP_SAVER19R20X  0b001U
+#define AARCH64_UNOP_SAVEFPLR	  0b01U
+#define AARCH64_UNOP_SAVEFPLRX	  0b10U
+#define AARCH64_UNOP_ALLOCM	  0b11000U
+#define AARCH64_UNOP_SAVEREGP	  0b110010U
+#define AARCH64_UNOP_SAVEREGPX	  0b110011U
+#define AARCH64_UNOP_SAVEREG	  0b110100U
+#define AARCH64_UNOP_SAVEREGX	  0b1101010U
+#define AARCH64_UNOP_SAVELRPAIR	  0b1101011U
+#define AARCH64_UNOP_SAVEFREGP	  0b1101100U
+#define AARCH64_UNOP_SAVEFREGPX	  0b1101101U
+#define AARCH64_UNOP_SAVEFREG	  0b1101110U
+#define AARCH64_UNOP_SAVEFREGX	  0b11011110U
+#define AARCH64_UNOP_ALLOCL	  0b11100000U
+#define AARCH64_UNOP_SETFP	  0b11100001U
+#define AARCH64_UNOP_ADDFP	  0b11100010U
+#define AARCH64_UNOP_NOP	  0b11100011U
+#define AARCH64_UNOP_END	  0b11100100U
+#define AARCH64_UNOP_ENDC	  0b11100101U
+#define AARCH64_UNOP_SAVENEXT	  0b11100110U
+#define AARCH64_UNOP_PACSIGNLR	  0b11111100U
+
+#endif /* OBJ_COFF_SEH_AARCH64_H.  */
diff --git a/gas/config/obj-coff-seh-shared.c b/gas/config/obj-coff-seh-shared.c
index f3afd981aca..132470c70e6 100644
--- a/gas/config/obj-coff-seh-shared.c
+++ b/gas/config/obj-coff-seh-shared.c
@@ -19,8 +19,13 @@ 
    Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
    02110-1301, USA.  */
 
+#if defined (COFFAARCH64)
+#include "obj-coff-seh-aarch64.h"
+typedef struct seh_aarch64_context seh_context_t;
+#else
 #include "obj-coff-seh.h"
 typedef struct seh_context seh_context_t;
+#endif
 
 /* Private segment collection list.  */
 struct seh_seg_list {
diff --git a/gas/config/obj-coff.c b/gas/config/obj-coff.c
index 7732c0af911..b08295019d8 100644
--- a/gas/config/obj-coff.c
+++ b/gas/config/obj-coff.c
@@ -55,7 +55,11 @@  static const char weak_altprefix[] = ".weak.";
 #endif /* TE_PE */
 
 #include "obj-coff-seh-shared.c"
+#if defined (COFFAARCH64)
+#include "obj-coff-seh-aarch64.c"
+#else
 #include "obj-coff-seh.c"
+#endif
 
 typedef struct
   {
diff --git a/gas/config/tc-aarch64.c b/gas/config/tc-aarch64.c
index cd76163488c..d9d3296a458 100644
--- a/gas/config/tc-aarch64.c
+++ b/gas/config/tc-aarch64.c
@@ -10375,6 +10375,16 @@  aarch64_cleanup (void)
     }
 }
 
+#if defined (OBJ_COFF)
+/* Called after all assembly has been done.  */
+
+void
+aarch64_md_finish (void)
+{
+  seh_aarch64_write_data();
+}
+#endif /* OBJ_COFF.  */
+
 #ifdef OBJ_ELF
 /* Remove any excess mapping symbols generated for alignment frags in
    SEC.  We may have created a mapping symbol before a zero byte
diff --git a/gas/config/tc-aarch64.h b/gas/config/tc-aarch64.h
index d1fb4c9058b..67f476a3dcc 100644
--- a/gas/config/tc-aarch64.h
+++ b/gas/config/tc-aarch64.h
@@ -82,6 +82,11 @@  struct aarch64_fix
 
 #define tc_frob_section(S) aarch64_frob_section (S)
 
+#if defined (OBJ_COFF)
+#define md_finish aarch64_md_finish
+extern void aarch64_md_finish (void);
+#endif
+
 /* The key used to sign a function's return address.  */
 enum pointer_auth_key {
   AARCH64_PAUTH_KEY_A,
@@ -363,6 +368,7 @@  extern void aarch64_handle_align (struct frag *);
 extern int tc_aarch64_regname_to_dw2regnum (char *regname);
 extern void tc_aarch64_frame_initial_instructions (void);
 extern bool aarch64_fix_adjustable (struct fix *);
+extern void seh_aarch64_write_data (void);
 
 #ifdef TE_PE
 
diff --git a/gas/write.c b/gas/write.c
index 9d0777051dd..903f858b4dc 100644
--- a/gas/write.c
+++ b/gas/write.c
@@ -1831,7 +1831,7 @@  set_symtab (void)
 #endif
 #endif
 
-static void
+void
 subsegs_finish_section (asection *s)
 {
   struct frchain *frchainP;
diff --git a/gas/write.h b/gas/write.h
index 7281930c2d6..0638e5958d6 100644
--- a/gas/write.h
+++ b/gas/write.h
@@ -188,5 +188,6 @@  extern fixS *fix_new_exp (fragS *, unsigned long, unsigned long,
 			  const expressionS *, int, bfd_reloc_code_real_type);
 extern void write_print_statistics (FILE *);
 extern void as_bad_subtract (fixS *);
+extern void subsegs_finish_section (asection *);
 
 #endif /* __write_h__ */