[v7,3/3] aarch64: 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
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
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__ */
new file mode 100644
@@ -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)
+{
+}
new file mode 100644
@@ -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. */
@@ -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 {
@@ -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
{
@@ -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
@@ -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
@@ -1831,7 +1831,7 @@ set_symtab (void)
#endif
#endif
-static void
+void
subsegs_finish_section (asection *s)
{
struct frchain *frchainP;
@@ -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__ */