[PATCH v5 3/5] aarch64: Implement Structured Exception Handling (SEH) on AArch64
Evgeny Karpov
evgeny.karpov@arm.com
Tue Feb 24 09:49:21 GMT 2026
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).
When .pdata/.xdata is emitted, the function size is required.
Function size is calculated as late as possible, however, code sections are
not fixed yet and frag_offset_fixed_p cannot be used.
Instead, frag_offset_ignore_align_p is used, which ignores code alignment.
It looks like an edge case, however it should be investigated and
resolved if necessary.
Signed-off-by: Evgeny Karpov <evgeny@kmaps.co>
ChangeLog:
* gas/config/obj-coff-seh-shared.c (defined): Update.
* gas/config/obj-coff.c (defined): 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 | 872 ++++++++++++++++++++++++++++++
gas/config/obj-coff-seh-aarch64.h | 265 +++++++++
gas/config/obj-coff-seh-shared.c | 5 +
gas/config/obj-coff.c | 4 +
4 files changed, 1146 insertions(+)
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..f5e9d4212da
--- /dev/null
+++ b/gas/config/obj-coff-seh-aarch64.c
@@ -0,0 +1,872 @@
+/* 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);
+ }
+}
+
+/* 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);
+
+ fragS *start_frag, *end_frag;
+ addressT start_value, end_value;
+ start_frag = symbol_get_frag_and_value (seh_ctx->start_addr, &start_value);
+ end_frag = symbol_get_frag_and_value (seh_ctx->end_addr, &end_value);
+ offsetT offset;
+ /* Function size is calculated as late as possible, however, code sections are
+ not fixed yet and frag_offset_fixed_p cannot be used.
+ It looks like an edge case, however it should be investigated and
+ resolved if necessary. */
+ frag_offset_ignore_align_p (end_frag, start_frag, &offset);
+ start_value += offset / OCTETS_PER_BYTE;
+ offset = end_value - start_value;
+
+ if (offset < 0)
+ as_bad (_("the function size expression for %s "
+ "does not evaluate to a non-negative value"),
+ S_GET_NAME (seh_ctx->start_addr));
+
+ const uintptr_t func_size = offset;
+
+ /* 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
+obj_coff_seh_do_final (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;
+ 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;
+ }
+}
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
{
More information about the Binutils
mailing list