[PATCH v8 1/1] aarch64: Implement Structured Exception Handling (SEH) on AArch64
Richard Earnshaw (foss)
Richard.Earnshaw@arm.com
Tue Jun 16 16:06:47 GMT 2026
A couple of nits in addition to Jan's comments, but this is ok once those are fixed.
R.
On 15/06/2026 17:06, 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 called in write_object_file.
> Before the change, obj_coff_generate_pdata was used only to validate
> syntax, which was sufficient for that purpose. However, that location
> seems incorrect, as it is too late to emit .pdata/.xdata records
> in the AArch64 case.
>
> md_finish has been declared for AArch64 and extended with
> seh_aarch64_write_data to emit .pdata/.xdata records after all
> assembly has been completed.
>
> Signed-off-by: Evgeny Karpov <evgeny@kmaps.co>
>
> gas/ChangeLog:
> * gas/config/obj-coff-seh-shared.c (defined): Update.
> (struct seh_seg_list): Use seh_context_t.
> * 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.
> * testsuite/gas/pe/pe.exp: Add SEH tests.
> * write.c: Update.
> * write.h (subsegs_finish_section): Update.
> * config/obj-coff-seh-aarch64.c: New file.
> * config/obj-coff-seh-aarch64.h: New file.
> * testsuite/gas/pe/seh-aarch64-error.l: New test.
> * testsuite/gas/pe/seh-aarch64-error.s: New test.
> * testsuite/gas/pe/seh-aarch64-large-func.d: New test.
> * testsuite/gas/pe/seh-aarch64-large-func.s: New test.
> * testsuite/gas/pe/seh-aarch64.d: New test.
> * testsuite/gas/pe/seh-aarch64.s: New test.
> ---
> gas/config/obj-coff-seh-aarch64.c | 971 ++++++++++++++++++
> gas/config/obj-coff-seh-aarch64.h | 251 +++++
> gas/config/obj-coff-seh-shared.c | 8 +-
> gas/config/obj-coff.c | 4 +
> gas/config/tc-aarch64.c | 10 +
> gas/config/tc-aarch64.h | 6 +
> gas/testsuite/gas/pe/pe.exp | 3 +
> gas/testsuite/gas/pe/seh-aarch64-error.l | 110 ++
> gas/testsuite/gas/pe/seh-aarch64-error.s | 128 +++
> gas/testsuite/gas/pe/seh-aarch64-large-func.d | 53 +
> gas/testsuite/gas/pe/seh-aarch64-large-func.s | 13 +
> gas/testsuite/gas/pe/seh-aarch64.d | 66 ++
> gas/testsuite/gas/pe/seh-aarch64.s | 49 +
> gas/write.c | 2 +-
> gas/write.h | 1 +
> 15 files changed, 1673 insertions(+), 2 deletions(-)
> create mode 100644 gas/config/obj-coff-seh-aarch64.c
> create mode 100644 gas/config/obj-coff-seh-aarch64.h
> create mode 100644 gas/testsuite/gas/pe/seh-aarch64-error.l
> create mode 100644 gas/testsuite/gas/pe/seh-aarch64-error.s
> create mode 100644 gas/testsuite/gas/pe/seh-aarch64-large-func.d
> create mode 100644 gas/testsuite/gas/pe/seh-aarch64-large-func.s
> create mode 100644 gas/testsuite/gas/pe/seh-aarch64.d
> create mode 100644 gas/testsuite/gas/pe/seh-aarch64.s
>
> diff --git a/gas/config/obj-coff-seh-aarch64.c b/gas/config/obj-coff-seh-aarch64.c
> new file mode 100644
> index 00000000000..b0d32c72d01
> --- /dev/null
> +++ b/gas/config/obj-coff-seh-aarch64.c
> @@ -0,0 +1,971 @@
> +/* 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_info {
> + const char *directive;
> + char reg_type;
> + unsigned char offset_bits;
> + unsigned char reg_bits;
> + unsigned char reg_max;
> + unsigned char code_bits;
> + unsigned char code;
> + unsigned char offset_shift;
> + unsigned char offset_addend;
> + unsigned char reg_shift;
> + unsigned char reg_addend;
> + unsigned char size;
> +};
> +
> +/* Unwind codes format for AArch64 are described at
> + https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170#unwind-codes
I think we should avoid deep links like this in source code - they have a nasty habit of stopping working at some point in the future, especially given this seems to refer to a specific version of msvc. A reasonable search engine will find a current link pretty quickly, so I don't think a full link is necessary.
> + and calculated in seh_aarch64_add_unwind_element function.
> + aarch64_unwind_code_data is indexed by the seh_aarch64_unwind_types enum. */
> +
> +static const struct aarch64_unwind_info
> +aarch64_unwind_code_data[] = {
> + {
> + .size = 1,
> + .code_bits = 3, .code = 0b000U,
0b as a prefix is (I believe) new in C23 (although many older versions of gcc support it). I think we try to make code compatible with c11 compilers, so please use hex. See eg PR28882.
> + .offset_bits = 5, .offset_shift = 4
> + },
> + {
> + .size = 2,
> + .code_bits = 5, .code = 0b11000U,
> + .offset_bits = 11, .offset_shift = 4
> + },
> + {
> + .size = 4,
> + .code_bits = 8, .code = 0b11100000U,
> + .offset_bits = 24, .offset_shift = 4
> + },
> + {
> + .directive = ".seh_save_reg",
> + .size = 2,
> + .code_bits = 6, .code = 0b110100U,
> + .offset_bits = 6, .offset_shift = 3,
> + .reg_bits = 4, .reg_addend = 19, .reg_type = 'x'
> + },
> + {
> + .directive = ".seh_save_reg_x",
> + .size = 2,
> + .code_bits = 7, .code = 0b1101010U,
> + .offset_bits = 5, .offset_shift = 3, .offset_addend = 1,
> + .reg_bits = 4, .reg_addend = 19, .reg_type = 'x'
> + },
> + {
> + .directive = ".seh_save_regp",
> + .size = 2,
> + .code_bits = 6, .code = 0b110010U,
> + .offset_bits = 6, .offset_shift = 3,
> + .reg_bits = 4, .reg_addend = 19, .reg_type = 'x', .reg_max = 29
> + },
> + {
> + .directive = ".seh_save_regp_x",
> + .size = 2,
> + .code_bits = 6, .code = 0b110011U,
> + .offset_bits = 6, .offset_shift = 3, .offset_addend = 1,
> + .reg_bits = 4, .reg_addend = 19, .reg_type = 'x', .reg_max = 29
> + },
> + {
> + .directive = ".seh_save_fregp",
> + .size = 2,
> + .code_bits = 7, .code = 0b1101100U,
> + .offset_bits = 6, .offset_shift = 3,
> + .reg_bits = 3, .reg_addend = 8, .reg_type = 'd'
> + },
> + {
> + .directive = ".seh_save_fregp_x",
> + .size = 2,
> + .code_bits = 7, .code = 0b1101101U,
> + .offset_bits = 6, .offset_shift = 3, .offset_addend = 1,
> + .reg_bits = 3, .reg_addend = 8, .reg_type = 'd'
> + },
> + {
> + .directive = ".seh_save_freg",
> + .size = 2,
> + .code_bits = 7, .code = 0b1101110U,
> + .offset_bits = 6, .offset_shift = 3,
> + .reg_bits = 3, .reg_addend = 8, .reg_type = 'd'
> + },
> + {
> + .directive = ".seh_save_freg_x",
> + .size = 2,
> + .code_bits = 8, .code = 0b11011110U,
> + .offset_bits = 5, .offset_shift = 3, .offset_addend = 1,
> + .reg_bits = 3, .reg_addend = 8, .reg_type = 'd'
> + },
> + {
> + .directive = ".seh_save_lrpair",
> + .size = 2,
> + .code_bits = 7, .code = 0b1101011U,
> + .offset_bits = 6, .offset_shift = 3,
> + .reg_bits = 3, .reg_shift = 1, .reg_addend = 19, .reg_type = 'x'
> + },
> + {
> + .directive = ".seh_save_fplr",
> + .size = 1,
> + .code_bits = 2, .code = 0b01U,
> + .offset_bits = 6, .offset_shift = 3
> + },
> + {
> + .directive = ".seh_save_fplr_x",
> + .size = 1,
> + .code_bits = 2, .code = 0b10U,
> + .offset_bits = 6, .offset_shift = 3, .offset_addend = 1
> + },
> + {
> + .directive = ".seh_save_r19r20_x",
> + .size = 1,
> + .code_bits = 3, .code = 0b001U,
> + .offset_bits = 5, .offset_shift = 3
> + },
> + {
> + .directive = ".seh_add_fp",
> + .size = 2,
> + .code_bits = 8, .code = 0b11100010U,
> + .offset_bits = 8, .offset_shift = 3
> + },
> + {
> + .directive = ".seh_set_fp",
> + .size = 1,
> + .code_bits = 8, .code = 0b11100001U
> + },
> + {
> + .directive = ".seh_save_next",
> + .size = 1,
> + .code_bits = 8, .code = 0b11100110U
> + },
> + {
> + .directive = ".seh_nop",
> + .size = 1,
> + .code_bits = 8, .code = 0b11100011U
> + },
> + {
> + .directive = ".seh_pac_sign_lr",
> + .size = 1,
> + .code_bits = 8, .code = 0b11111100U
> + },
> + {
> + .size = 1,
> + .code_bits = 8, .code = 0b11100100U
> + },
> +};
> +
> +/* 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, "@1") == 0)
> + seh_ctx_cur->handler.X_add_number = 1;
> + else if (strcasecmp (symbol_name, "@0")
> + && strcasecmp (symbol_name, "@null"))
> + 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);
> + seh_ctx_cur->handler_data_xdata_addr = symbol_temp_new_now();
> +}
> +
> +/* Switch back to the code section. */
> +static void
> +obj_coff_seh_code (int ignored ATTRIBUTE_UNUSED)
> +{
> + subseg_set (seh_ctx_cur->code_seg, 0);
> +}
> +
> +/* Obtain available unwind element. */
> +static void
> +seh_aarch64_add_unwind_element (const seh_aarch64_unwind_types unwind_type,
> + unsigned offset, unsigned reg)
> +{
> + const struct aarch64_unwind_info *info
> + = aarch64_unwind_code_data + unwind_type;
> + unsigned value_offset_bits = 0;
> +
> + if ((seh_ctx_cur->unwind_codes_byte_count
> + + info->size) > AARCH64_MAX_UNWIND_CODES_SIZE)
> + as_bad (_("no unwind element available."));
> +
> + unsigned value = 0;
> +
> + if (info->offset_bits)
> + {
> + const unsigned divided_by = 1u << info->offset_shift;
> + if (offset & (divided_by - 1))
> + as_bad (_("offset should be divided by %u"), divided_by);
> + offset = (offset >> info->offset_shift) - info->offset_addend;
> + if (offset >= (1u << info->offset_bits))
> + as_bad (_("offset overflows expected range"));
> + value |= offset << value_offset_bits;
> + value_offset_bits += info->offset_bits;
> + }
> +
> + if (info->reg_bits)
> + {
> + const unsigned regn_divided_by = 1u << info->reg_shift;
> + reg -= info->reg_addend;
> + if (reg & (regn_divided_by - 1))
> + as_bad (_("unexpected register number"));
> + reg >>= info->reg_shift;
> + if (reg >= (1u << info->reg_bits))
> + as_bad (_("unexpected register number"));
> + value |= reg << value_offset_bits;
> + value_offset_bits += info->reg_bits;
> + }
> +
> + const unsigned code = info->code;
> + gas_assert (code < (1u << info->code_bits));
> + value |= code << value_offset_bits;
> +
> + seh_aarch64_unwind_code *element;
> + element = seh_ctx_cur->unwind_codes + seh_ctx_cur->unwind_codes_count++;
> + element->type = unwind_type;
> + element->value = value;
> +
> + seh_ctx_cur->unwind_codes_byte_count += 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"));
> + return;
> + }
> +
> + 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 ();
> +
> + if (seh_ctx_cur->epilogue_scopes_count >= AARCH64_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_startepilogue 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 = 0;
> + 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_info *info
> + = aarch64_unwind_code_data + type;
> +
> + SKIP_WHITESPACE ();
> +
> + char *symbol_name = NULL;
> + unsigned reg = -1;
> +
> + if (info->reg_bits)
> + {
> + char name_end = get_symbol_name (&symbol_name);
> + if (info->reg_type != *symbol_name)
> + as_bad ("unexpected register name");
> +
> + reg = atoi (symbol_name + 1);
> + (void) restore_line_pointer (name_end);
> +
> + if (!skip_whitespace_and_comma (1))
> + return;
> +
> + const unsigned reg_max = info->reg_max ? info->reg_max: 30;
> + if (reg > reg_max)
> + as_bad (_("unexpected register number"));
> + }
> +
> + offsetT off = -1;
> + if (info->offset_bits)
> + {
> + off = get_absolute_expression ();
> +
> + if (off < 0)
> + as_bad (_("offset is negative"));
> + }
> +
> + demand_empty_rest_of_line ();
> +
> + if (!in_seh_proc)
> + {
> + as_bad (_("SEH entry has not been found (missing .seh_proc)"));
> + return;
> + }
> +
> + if (!info->directive || !seh_validate_seg (info->directive))
> + return;
> +
> + 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 ();
> +
> + if (!in_seh_proc)
> + {
> + as_bad (_("SEH entry has not been found (missing .seh_proc)"));
> + return;
> + }
> +
> + 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 is out of range"));
> +}
> +
> +/* Data writing routines. */
> +static void
> +seh_aarch64_emit_epilogue_scopes (const seh_context *seh_ctx,
> + const uint64_t fragment_offset,
> + const unsigned prologue_size,
> + const unsigned first_fragment_scope,
> + const unsigned last_fragment_scope,
> + const bool has_phantom_prologue)
> +{
> + 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
> + - prologue_size;
> + if (has_phantom_prologue)
> + {
> + if (start_index_offset == 0)
> + as_bad (_("start index offset for the epilogue cannot be 0 when "
> + "phantom prologue 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 prologue_size,
> + const unsigned first_epilogue_index,
> + const unsigned last_epilogue_index,
> + const bool has_phantom_prologue)
> +{
> + unsigned total_byte_count = 0;
> +
> + if (has_phantom_prologue)
> + {
> + ++total_byte_count;
> + const unsigned endc_code = 0b11100101U;
Another binary literal.
> + md_number_to_chars (frag_more (1), endc_code, 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_data[code->type].size;
> + unwind_bytes_offset += byte_count;
> +
> + if (unwind_bytes_offset > last_epilogue_index)
> + break;
> +
> + if (unwind_bytes_offset > prologue_size
> + && unwind_bytes_offset <= first_epilogue_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. */
> + const unsigned required_padding = (-total_byte_count) % 4;
> + if (required_padding)
> + {
> + /* Use the nop unwind code for alignment. */
> + const uint32_t nop_chain = 0xe3e3e3e3;
> +
> + md_number_to_chars (frag_more (required_padding), nop_chain,
> + required_padding);
> + }
> +}
> +
> +static void
> +seh_aarch64_emit_xdata_record(struct seh_aarch64_context *seh_ctx,
nit: space before parenthesis.
> + const uintptr_t frag_size,
> + const uintptr_t fragment_offset,
> + const unsigned header_size,
> + const unsigned prologue_size,
> + const unsigned prologue_insn_count,
> + const bool has_phantom_prologue,
> + const unsigned first_fragment_scope,
> + const unsigned last_fragment_scope,
> + const unsigned first_epilogue_index,
> + const unsigned last_epilogue_index)
> +{
> + const seh_aarch64_xdata_header *header = &seh_ctx->xdata_header;
> + md_number_to_chars (frag_more (header_size), seh_ctx->xdata_header_value,
> + header_size);
> +
> + if (header->ext_epilogue_count && !header->e)
> + {
> + seh_aarch64_emit_epilogue_scopes (seh_ctx,
> + fragment_offset, prologue_size,
> + first_fragment_scope,
> + last_fragment_scope,
> + has_phantom_prologue);
> + if (has_phantom_prologue)
> + {
> + const uint32_t epilogue_start_index_encoded = 1 << 22;
> + const uint32_t epilogue_start_offset_encoded
> + = (frag_size - prologue_insn_count * 4) >> 2;
> + md_number_to_chars (frag_more (4),
> + epilogue_start_index_encoded
> + | epilogue_start_offset_encoded, 4);
> + }
> + }
> +
> + if (header->ext_code_words)
> + seh_aarch64_emit_unwind_codes (seh_ctx, prologue_size, first_epilogue_index,
> + last_epilogue_index, has_phantom_prologue);
> +
> + 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);
> +
> + /* Emit the fragment offset. */
> + md_number_to_chars (frag_more (4), fragment_offset, 4);
> +
> + /* Use the same SEH handler data for all fragments.
> + The SEH handler data is emitted after the last fragment. */
> + expressionS exp;
> + memset (&exp, 0, sizeof (expressionS));
> + exp.X_op = O_symbol_rva;
> + exp.X_add_symbol = seh_ctx->handler_data_xdata_addr;
> + emit_expr (&exp, 4);
> + }
> +}
> +
> +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;
> +
> + unsigned prologue_insn_count = 0;
> + for (unsigned i = 0; i < seh_ctx->unwind_codes_count; ++i)
> + {
> + if (seh_ctx->unwind_codes[i].type == unwind_end)
> + {
> + prologue_insn_count = i + 1;
> + break;
> + }
> + }
> +
> + unsigned prologue_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;
> +
> + /* Large functions (>= 1MB) will be split into multiple fragments.
> + However, it is expected the most of the functions will have only one
> + fragment. This loop iterates fragments and emit them. */
> + while (true)
> + {
> + fragment->xdata_addr = symbol_temp_new_now ();
> + fragment->offset = fragment_offset;
> + fragment->next = NULL;
> +
> + /* Calculate current fragment size. */
> + uintptr_t frag_size = func_size - fragment_offset;
> + if (frag_size > max_frag_size)
> + frag_size = max_frag_size;
> +
> + const bool is_last_frag = (fragment_offset + frag_size) == func_size;
> +
> + /* If it is a fragmented function, the epilogue range should be calculated
> + and will be emitted for the current fragment, otherwise all epilogues
> + will be emitted. */
> + 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;
> +
> + /* Initialize the .xdata record. */
> + 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;
> +
> + /* Calculate epilogue indexes for the current fragment. */
> + unsigned first_epilogue_index = 0;
> + unsigned last_epilogue_index = 0;
> + if (!header->ext_epilogue_count)
> + {
> + first_epilogue_index = prologue_size;
> + last_epilogue_index = prologue_size;
> + }
> + else
> + {
> + const seh_aarch64_epilogue_scope *scope;
> + scope = scopes + first_fragment_scope;
> + first_epilogue_index = scope->epilogue_start_index;
> + last_epilogue_index = seh_ctx->unwind_codes_byte_count;
> + }
> +
> + /* Calculate how many unwind bytes will be emitted in .xdata record. */
> + unsigned unwind_bytes = prologue_size;
> +
> + /* Check if current fragment has a phantom prologue. If yes, then
> + the unwinding size should be adjusted. */
> + const bool has_phantom_prologue = is_fragmented_function && is_last_frag;
> + if (has_phantom_prologue && unwind_bytes)
> + {
> + /* One more epilogue scope and unwind code are emitted with phantom
> + prologue. */
> + 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;
> +
> + /* Check if short or extended header for a .xdata record should be
> + used. */
> + unsigned header_size = 8;
> + if ((header->ext_code_words != 0 || header->ext_epilogue_count != 0)
> + && header->ext_code_words < 32
> + && header->ext_epilogue_count < 32)
> + {
> + header_size = 4;
> + 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_prologue)
> + 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;
> + }
> + }
> + }
> +
> + /* Emit a .xdata record for the current fragment. */
> + seh_aarch64_emit_xdata_record(seh_ctx, frag_size, fragment_offset,
> + header_size,
> + prologue_size, prologue_insn_count,
> + has_phantom_prologue,
> + first_fragment_scope, last_fragment_scope,
> + first_epilogue_index, last_epilogue_index);
> +
> + fragment_offset += frag_size;
> + /* Exit the loop if it is the latest fragment. */
> + if (is_last_frag)
> + break;
> +
> + /* Allocate a new fragment that will be used also for emitting a .pdata
> + record. */
> + 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)"));
> + return;
> + }
> +
> + 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..226e40c2312
> --- /dev/null
> +++ b/gas/config/obj-coff-seh-aarch64.h
> @@ -0,0 +1,251 @@
> +/* 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.
> +
> + The current implementation does not include:
> + - Packed .pdata record.
> + - Support for AdvSIMD and SVE.
> + - Epilogue start index different than 0. */
> +
> +#ifndef OBJ_COFF_SEH_AARCH64_H
> +#define OBJ_COFF_SEH_AARCH64_H
> +
> +/* Unwind codes for AArch64 are described at
> + https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170#unwind-codes. */
> +
> +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 prologue 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}, \
> + \
> + /* Switch back to the code section. */ \
> + {"seh_code", obj_coff_seh_code, 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;
> +
> + symbolS *handler_data_xdata_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;
> + /* The function fragments. */
> + seh_aarch64_func_fragment func_fragment;
> +} seh_context;
> +
> +#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 717a7d345ef..b99a6bb67ee 100644
> --- a/gas/config/obj-coff-seh-shared.c
> +++ b/gas/config/obj-coff-seh-shared.c
> @@ -19,7 +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 {
> @@ -28,7 +34,7 @@ struct seh_seg_list {
> char *seg_name;
> };
>
> -static struct seh_context *seh_ctx_cur = NULL;
> +static seh_context_t *seh_ctx_cur = NULL;
>
> static htab_t seh_hash;
>
> 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 fb58bcca9b1..c672f284a48 100644
> --- a/gas/config/tc-aarch64.c
> +++ b/gas/config/tc-aarch64.c
> @@ -10459,6 +10459,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..6446ac8efb8 100644
> --- a/gas/config/tc-aarch64.h
> +++ b/gas/config/tc-aarch64.h
> @@ -82,6 +82,12 @@ 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);
> +extern void seh_aarch64_write_data (void);
> +#endif
> +
> /* The key used to sign a function's return address. */
> enum pointer_auth_key {
> AARCH64_PAUTH_KEY_A,
> diff --git a/gas/testsuite/gas/pe/pe.exp b/gas/testsuite/gas/pe/pe.exp
> index e25de3cfa9d..cc7701587ac 100644
> --- a/gas/testsuite/gas/pe/pe.exp
> +++ b/gas/testsuite/gas/pe/pe.exp
> @@ -63,6 +63,9 @@ if ([istarget "x86_64-*-mingw*"]) then {
> # This test is only for AArch64
> if {[istarget "aarch64-*-pe*"] || [istarget "aarch64-*-mingw*"]} {
> run_dump_test "pe-aarch64"
> + run_dump_test "seh-aarch64"
> + run_dump_test "seh-aarch64-large-func"
> + run_list_test "seh-aarch64-error" ""
> }
>
> # Big obj
> diff --git a/gas/testsuite/gas/pe/seh-aarch64-error.l b/gas/testsuite/gas/pe/seh-aarch64-error.l
> new file mode 100644
> index 00000000000..2d7f2c055f6
> --- /dev/null
> +++ b/gas/testsuite/gas/pe/seh-aarch64-error.l
> @@ -0,0 +1,110 @@
> +.*: Assembler messages:
> +.*:2: Error: .seh_endprologue used outside of .seh_proc block
> +.*:3: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:4: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:5: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:6: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:7: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:8: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:9: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:10: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:11: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:12: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:13: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:14: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:15: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:16: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:17: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:18: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:19: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:20: Error: SEH entry has not been found \(missing .seh_proc\)
> +.*:21: Error: .seh_endproc used without .seh_proc
> +.*:23: Error: .seh_proc requires function label name
> +.*:24: Error: previous SEH entry not closed \(missing .seh_endproc\)
> +.*:29: Error: offset overflows expected range
> +.*:30: Error: offset should be divided by 16
> +.*:31: Error: .seh_stackalloc offset is out of range
> +.*:33: Error: unexpected register number
> +.*:34: Error: unexpected register number
> +.*:35: Error: unexpected register name
> +.*:36: Error: offset is negative
> +.*:36: Error: offset overflows expected range
> +.*:38: Error: offset should be divided by 8
> +.*:39: Error: offset overflows expected range
> +.*:41: Error: unexpected register number
> +.*:42: Error: unexpected register number
> +.*:43: Error: unexpected register name
> +.*:44: Error: offset is negative
> +.*:44: Error: offset overflows expected range
> +.*:46: Error: offset should be divided by 8
> +.*:47: Error: offset overflows expected range
> +.*:49: Error: unexpected register number
> +.*:50: Error: unexpected register number
> +.*:51: Error: unexpected register name
> +.*:52: Error: unexpected register number
> +.*:53: Error: offset is negative
> +.*:53: Error: offset overflows expected range
> +.*:55: Error: offset should be divided by 8
> +.*:56: Error: offset overflows expected range
> +.*:58: Error: unexpected register number
> +.*:59: Error: unexpected register number
> +.*:60: Error: unexpected register name
> +.*:61: Error: unexpected register number
> +.*:62: Error: offset is negative
> +.*:62: Error: offset overflows expected range
> +.*:64: Error: offset should be divided by 8
> +.*:65: Error: offset overflows expected range
> +.*:67: Error: unexpected register number
> +.*:67: Error: unexpected register number
> +.*:68: Error: unexpected register number
> +.*:69: Error: unexpected register name
> +.*:70: Error: unexpected register number
> +.*:71: Error: offset is negative
> +.*:71: Error: offset overflows expected range
> +.*:73: Error: offset should be divided by 8
> +.*:74: Error: offset overflows expected range
> +.*:76: Error: unexpected register number
> +.*:77: Error: unexpected register number
> +.*:78: Error: unexpected register name
> +.*:79: Error: offset is negative
> +.*:79: Error: offset overflows expected range
> +.*:81: Error: offset should be divided by 8
> +.*:82: Error: offset overflows expected range
> +.*:84: Error: unexpected register number
> +.*:85: Error: unexpected register number
> +.*:86: Error: unexpected register name
> +.*:87: Error: offset is negative
> +.*:87: Error: offset overflows expected range
> +.*:89: Error: offset should be divided by 8
> +.*:90: Error: offset overflows expected range
> +.*:92: Error: unexpected register number
> +.*:93: Error: unexpected register number
> +.*:94: Error: unexpected register name
> +.*:95: Error: offset is negative
> +.*:95: Error: offset overflows expected range
> +.*:97: Error: offset should be divided by 8
> +.*:98: Error: offset overflows expected range
> +.*:100: Error: unexpected register number
> +.*:101: Error: unexpected register number
> +.*:102: Error: unexpected register name
> +.*:103: Error: offset is negative
> +.*:103: Error: offset overflows expected range
> +.*:105: Error: offset should be divided by 8
> +.*:106: Error: offset overflows expected range
> +.*:108: Error: offset is negative
> +.*:108: Error: offset overflows expected range
> +.*:110: Error: offset should be divided by 8
> +.*:111: Error: offset overflows expected range
> +.*:113: Error: offset is negative
> +.*:113: Error: offset overflows expected range
> +.*:115: Error: offset should be divided by 8
> +.*:116: Error: offset overflows expected range
> +.*:118: Error: offset is negative
> +.*:118: Error: offset overflows expected range
> +.*:120: Error: offset should be divided by 8
> +.*:121: Error: offset overflows expected range
> +.*:123: Error: offset is negative
> +.*:123: Error: offset overflows expected range
> +.*:125: Error: offset should be divided by 8
> +.*:126: Error: offset overflows expected range
> +.*: Error: open SEH entry at end of file \(missing .seh_endproc\)
> diff --git a/gas/testsuite/gas/pe/seh-aarch64-error.s b/gas/testsuite/gas/pe/seh-aarch64-error.s
> new file mode 100644
> index 00000000000..dc4118b7390
> --- /dev/null
> +++ b/gas/testsuite/gas/pe/seh-aarch64-error.s
> @@ -0,0 +1,128 @@
> + .text
> + .seh_endprologue
> + .seh_stackalloc 16
> + .seh_save_reg x19, 0
> + .seh_save_reg_x x19, 16
> + .seh_save_regp x19, 32
> + .seh_save_regp_x x19, 48
> + .seh_save_lrpair x19, 64
> + .seh_save_fregp d8, 80
> + .seh_save_fregp_x d8, 96
> + .seh_save_freg d8, 112
> + .seh_save_freg_x d8, 128
> + .seh_save_fplr 144
> + .seh_save_fplr_x 160
> + .seh_save_r19r20_x 176
> + .seh_add_fp 192
> + .seh_nop
> + .seh_pac_sign_lr
> + .seh_set_fp
> + .seh_save_next
> + .seh_endproc
> +
> + .seh_proc
> + .seh_proc foo
> + .seh_endproc
> +
> + .seh_proc foo
> +
> + .seh_stackalloc -16
> + .seh_stackalloc 268435455
> + .seh_stackalloc 268435456
> +
> + .seh_save_reg x18, 0
> + .seh_save_reg x31, 0
> + .seh_save_reg d19, 0
> + .seh_save_reg x19, -8
> + .seh_save_reg x19, 504
> + .seh_save_reg x19, 511
> + .seh_save_reg x19, 512
> +
> + .seh_save_reg_x x18, 16
> + .seh_save_reg_x x31, 16
> + .seh_save_reg_x d19, 16
> + .seh_save_reg_x x19, -8
> + .seh_save_reg_x x19, 256
> + .seh_save_reg_x x19, 263
> + .seh_save_reg_x x19, 264
> +
> + .seh_save_regp x18, 32
> + .seh_save_regp x31, 32
> + .seh_save_regp d19, 32
> + .seh_save_regp x30, 32
> + .seh_save_regp x19, -8
> + .seh_save_regp x19, 504
> + .seh_save_regp x19, 511
> + .seh_save_regp x19, 512
> +
> + .seh_save_regp_x x18, 48
> + .seh_save_regp_x x31, 48
> + .seh_save_regp_x d19, 48
> + .seh_save_regp_x x30, 48
> + .seh_save_regp_x x19, -8
> + .seh_save_regp_x x19, 512
> + .seh_save_regp_x x19, 519
> + .seh_save_regp_x x19, 520
> +
> + .seh_save_lrpair x18, 64
> + .seh_save_lrpair x31, 64
> + .seh_save_lrpair d19, 64
> + .seh_save_lrpair x20, 64
> + .seh_save_lrpair x19, -8
> + .seh_save_lrpair x19, 504
> + .seh_save_lrpair x19, 511
> + .seh_save_lrpair x19, 512
> +
> + .seh_save_fregp d7, 80
> + .seh_save_fregp d16, 80
> + .seh_save_fregp x8, 80
> + .seh_save_fregp d8, -8
> + .seh_save_fregp d8, 504
> + .seh_save_fregp d8, 511
> + .seh_save_fregp d8, 512
> +
> + .seh_save_fregp_x d7, 96
> + .seh_save_fregp_x d16, 96
> + .seh_save_fregp_x x8, 96
> + .seh_save_fregp_x d8, -8
> + .seh_save_fregp_x d8, 512
> + .seh_save_fregp_x d8, 519
> + .seh_save_fregp_x d8, 520
> +
> + .seh_save_freg d7, 112
> + .seh_save_freg d16, 112
> + .seh_save_freg x8, 112
> + .seh_save_freg d8, -8
> + .seh_save_freg d8, 504
> + .seh_save_freg d8, 511
> + .seh_save_freg d8, 512
> +
> + .seh_save_freg_x d7, 128
> + .seh_save_freg_x d16, 128
> + .seh_save_freg_x x8, 128
> + .seh_save_freg_x d8, -8
> + .seh_save_freg_x d8, 256
> + .seh_save_freg_x d8, 263
> + .seh_save_freg_x d8, 264
> +
> + .seh_save_fplr -8
> + .seh_save_fplr 504
> + .seh_save_fplr 511
> + .seh_save_fplr 512
> +
> + .seh_save_fplr_x -8
> + .seh_save_fplr_x 512
> + .seh_save_fplr_x 519
> + .seh_save_fplr_x 520
> +
> + .seh_save_r19r20_x -8
> + .seh_save_r19r20_x 248
> + .seh_save_r19r20_x 255
> + .seh_save_r19r20_x 256
> +
> + .seh_add_fp -8
> + .seh_add_fp 2040
> + .seh_add_fp 2047
> + .seh_add_fp 2048
> +
> + .seh_endprologue
> diff --git a/gas/testsuite/gas/pe/seh-aarch64-large-func.d b/gas/testsuite/gas/pe/seh-aarch64-large-func.d
> new file mode 100644
> index 00000000000..61338de5145
> --- /dev/null
> +++ b/gas/testsuite/gas/pe/seh-aarch64-large-func.d
> @@ -0,0 +1,53 @@
> +#objdump: -s -j .xdata
> +#name: PEP aarch64 SEH large function
> +
> +.*: file format pe-aarch64-little
> +
> +Contents of section .xdata:
> +
> +# .xdata SEH record
> +# 0x0813ffff code-words: 1
> +# epilogue-count: 0
> +# single-epilogue-in-header: 0
> +# exception-data: 1
> +# version: 0
> +# function-size: 1048572
> +# 0x01 .seh_stackalloc 16
> +# 0xe4 end
> +# 0xe3 nop
> +# 0xe3 nop
> +# 0x00000000 seh-handler
> +# 0x00000000 fragment-offset
> +# 0x0000003c seh-handle-data-address
> +# 0x0813ffff code-words: 1
> +# epilogue-count: 0
> +# single-epilogue-in-header: 0
> +# exception-data: 1
> +# version: 0
> +# function-size: 1048572
> +# 0x01 .seh_stackalloc 16
> +# 0xe4 end
> +# 0xe3 nop
> +# 0xe3 nop
> +# 0x00000000 seh-handler
> +# 0x000ffffc fragment-offset
> +# 0x0000003c seh-handle-data-address
> +# 0x087002ca code-words: 1
> +# epilogue-count: 0
> +# single-epilogue-in-header: 0
> +# exception-data: 1
> +# version: 0
> +# function-size: 2856
> +# 0xe5 end_c
> +# 0x01 .seh_stackalloc 16
> +# 0xe4 end
> +# 0xe3 nop
> +# 0x00000000 seh-handler
> +# 0x001ffff8 fragment-offset
> +# 0x0000003c seh-handle-data-address
> +# 0x00000001 long 1 (seh_handlerdata)
> +
> + 0000 ffff1308 01e4e3e3 00000000 00000000 .*
> + 0010 3c000000 ffff1308 01e4e3e3 00000000 .*
> + 0020 fcff0f00 3c000000 ca027008 e501e4e3 .*
> + 0030 00000000 f8ff1f00 3c000000 01000000 .*
> \ No newline at end of file
> diff --git a/gas/testsuite/gas/pe/seh-aarch64-large-func.s b/gas/testsuite/gas/pe/seh-aarch64-large-func.s
> new file mode 100644
> index 00000000000..4e7d4772b65
> --- /dev/null
> +++ b/gas/testsuite/gas/pe/seh-aarch64-large-func.s
> @@ -0,0 +1,13 @@
> + .text
> + .seh_proc foo
> +foo:
> + .seh_stackalloc 16
> + .seh_endprologue
> + .rept 2100000 / 4
> + nop
> + .endr
> + .seh_handler _ZN9exception6handleEPvS0_S0_S0_, @except
> + .seh_handlerdata
> + .long 1
> + .seh_code
> + .seh_endproc
> diff --git a/gas/testsuite/gas/pe/seh-aarch64.d b/gas/testsuite/gas/pe/seh-aarch64.d
> new file mode 100644
> index 00000000000..218366ab6b0
> --- /dev/null
> +++ b/gas/testsuite/gas/pe/seh-aarch64.d
> @@ -0,0 +1,66 @@
> +#objdump: -s -j .xdata
> +#name: PEP aarch64 SEH
> +
> +.*: file format pe-aarch64-little
> +
> +Contents of section .xdata:
> +
> +# .xdata SEH record
> +# 0x98700000 code-words: 19
> +# epilogue-count: 1
> +# single-epilogue-in-header: 1
> +# exception-data: 1
> +# version: 0
> +# function-size: 0
> +# 0xe6 .seh_save_next
> +# 0xe1 .seh_save_fp
> +# 0xfc .seh_pac_sign_lr
> +# 0xe3 .seh_nop
> +# 0xe218 .seh_add_fp 192
> +# 0x36 .seh_save_r19r20_x 176
> +# 0x93 .seh_save_fplr_x 160
> +# 0x52 .seh_save_fplr 144
> +# 0xdeef .seh_save_freg_x d15, 128
> +# 0xde2f .seh_save_freg_x d9, 128
> +# 0xde0f .seh_save_freg_x d8, 128
> +# 0xddce .seh_save_freg d15, 112
> +# 0xdc4e .seh_save_freg d9, 112
> +# 0xdc0e .seh_save_freg d8, 112
> +# 0xdbcb .seh_save_fregp_x d15, 96
> +# 0xda4b .seh_save_fregp_x d9, 96
> +# 0xda0b .seh_save_fregp_x d8, 96
> +# 0xd9ca .seh_save_fregp d15, 80
> +# 0xd84a .seh_save_fregp d9, 80
> +# 0xd80a .seh_save_fregp d8, 80
> +# 0xd748 .seh_save_lrpair x29, 64
> +# 0xd648 .seh_save_lrpair x21, 64
> +# 0xd608 .seh_save_lrpair x19, 64
> +# 0xce85 .seh_save_regp_x x29, 48
> +# 0xcc45 .seh_save_regp_x x20, 48
> +# 0xcc05 .seh_save_regp_x x19, 48
> +# 0xca84 .seh_save_regp x29, 32
> +# 0xc844 .seh_save_regp x20, 32
> +# 0xc804 .seh_save_regp x19, 32
> +# 0xd561 .seh_save_reg_x x30, 16
> +# 0xd421 .seh_save_reg_x x20, 16
> +# 0xd401 .seh_save_reg_x x19, 16
> +# 0xd2c0 .seh_save_reg x30, 0
> +# 0xd040 .seh_save_reg x20, 0
> +# 0xd000 .seh_save_reg x19, 0
> +# 0xe0ffffff .seh_stackalloc 268435440
> +# 0xe0000800 .seh_stackalloc 32768
> +# 0xc020 .seh_stackalloc 512
> +# 0x01 .seh_stackalloc 16
> +# 0xe4 nop
> +# 0xe4 nop
> +# 0x00000000 seh-handler
> +# 0x00000000 fragment-offset
> +# 0x0000005c seh-handle-data-address
> +# 0x00000001 long 1 (seh_handlerdata)
> +
> + 0000 00007098 e6e1fce3 e2183693 52deefde .*
> + 0010 2fde0fdd cedc4edc 0edbcbda 4bda0bd9 .*
> + 0020 cad84ad8 0ad748d6 48d608ce 85cc45cc .*
> + 0030 05ca84c8 44c804d5 61d421d4 01d2c0d0 .*
> + 0040 40d000e0 ffffffe0 000800c0 2001e4e4 .*
> + 0050 00000000 00000000 5c000000 01000000 .*
> \ No newline at end of file
> diff --git a/gas/testsuite/gas/pe/seh-aarch64.s b/gas/testsuite/gas/pe/seh-aarch64.s
> new file mode 100644
> index 00000000000..5868a7f57dc
> --- /dev/null
> +++ b/gas/testsuite/gas/pe/seh-aarch64.s
> @@ -0,0 +1,49 @@
> + .text
> + .seh_proc foo
> + .seh_stackalloc 16
> + .seh_stackalloc 512
> + .seh_stackalloc 32768
> + .seh_stackalloc 268435440
> + .seh_save_reg x19, 0
> + .seh_save_reg x20, 0
> + .seh_save_reg x30, 0
> + .seh_save_reg_x x19, 16
> + .seh_save_reg_x x20, 16
> + .seh_save_reg_x x30, 16
> + .seh_save_regp x19, 32
> + .seh_save_regp x20, 32
> + .seh_save_regp x29, 32
> + .seh_save_regp_x x19, 48
> + .seh_save_regp_x x20, 48
> + .seh_save_regp_x x29, 48
> + .seh_save_lrpair x19, 64
> + .seh_save_lrpair x21, 64
> + .seh_save_lrpair x29, 64
> + .seh_save_fregp d8, 80
> + .seh_save_fregp d9, 80
> + .seh_save_fregp d15, 80
> + .seh_save_fregp_x d8, 96
> + .seh_save_fregp_x d9, 96
> + .seh_save_fregp_x d15, 96
> + .seh_save_freg d8, 112
> + .seh_save_freg d9, 112
> + .seh_save_freg d15, 112
> + .seh_save_freg_x d8, 128
> + .seh_save_freg_x d9, 128
> + .seh_save_freg_x d15, 128
> + .seh_save_fplr 144
> + .seh_save_fplr_x 160
> + .seh_save_r19r20_x 176
> + .seh_add_fp 192
> + .seh_nop
> + .seh_pac_sign_lr
> + .seh_set_fp
> + .seh_save_next
> + .seh_endprologue
> + .seh_handler _ZN9exception6handleEPvS0_S0_S0_, @except
> + .seh_handlerdata
> + .long 1
> + .seh_code
> + .seh_startepilogue
> + .seh_endepilogue
> + .seh_endproc
> diff --git a/gas/write.c b/gas/write.c
> index 9514c3df42e..b1a0d00a275 100644
> --- a/gas/write.c
> +++ b/gas/write.c
> @@ -1839,7 +1839,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 bc5260a2efd..69072cd57e2 100644
> --- a/gas/write.h
> +++ b/gas/write.h
> @@ -191,5 +191,6 @@ extern void as_bad_subtract (fixS *);
> struct segment_info_struct;
> extern fragS *get_frag_for_address (fragS *, const struct segment_info_struct *,
> addressT);
> +extern void subsegs_finish_section (asection *);
>
> #endif /* __write_h__ */
More information about the Binutils
mailing list