[PATCH v12 1/1] aarch64: Implement Structured Exception Handling (SEH) on AArch64
Alice Carlotti
alice.carlotti@arm.com
Mon Jul 20 03:24:19 GMT 2026
On Thu, Jul 16, 2026 at 05:49:19PM +0200, 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>
>
Unless I find something else I've overlooked, I think the comments below cover
all the issues that need addressing before this can be merged.
There's definitely room for further optimisation, but that can be handled
separately in future. I also haven't looked too closely at the tests, but I
think they're good enough for now, and they'd likely need adjusting anyway
if/when we implement better unwind data compression.
...
> +static void
> +seh_aarch64_emit_xdata_record (struct seh_aarch64_context *seh_ctx,
> + const uintptr_t frag_size,
> + const uintptr_t fragment_offset,
> + const unsigned first_fragment_scope,
> + const unsigned last_fragment_scope)
> +{
> + unsigned epilogue_count = last_fragment_scope - first_fragment_scope;
> +
> + /* Calculate how many unwind bytes will be emitted in .xdata record. */
> + unsigned unwind_bytes = seh_ctx->unwind_codes_byte_count;
> +
> + /* Check if current fragment has a phantom prologue. If yes, then
> + the unwinding size should be adjusted. */
> + const bool has_phantom_prologue = fragment_offset != 0;
> + if (has_phantom_prologue && unwind_bytes)
The list of unwind codes will always include at least one `end` unwind code for
the prologue (phantom or non-phantom), even after any future optimisations are
applied. So any checks that unwind_bytes or code_words are nonzero can be
dropped (or replaced by a single assert that unwind_bytes is zero at the start
of the function).
> + {
> + /* One more epilogue scope and unwind code are emitted with phantom
> + prologue. */
> + unwind_bytes += 1;
> + ++epilogue_count;
What is this epilogue_count increment doing? You don't seem to emit any extra
epilogue scope to match the increased count, so I think this increment needs to
be removed.
> + }
> +
> + /* Calculate the number of code words with 4-byte alignment. */
> + unsigned code_words = (unwind_bytes + 3) / 4;
> +
> + /* Initialize the .xdata header. */
> + const unsigned char has_exception_data_shift = 20;
> + const unsigned char single_epilog_shift = 21;
> + const unsigned char epilogue_count_shift = 22;
> + const unsigned char code_words_shift = 27;
> + const unsigned char ext_epilogue_count_shift = 32;
> + const unsigned char ext_code_words_shift = 48;
> + const uint32_t func_length_encoded = frag_size >> 2;
> + uint64_t header = 0;
> + header |= func_length_encoded;
> + header |= seh_ctx->has_exception_data << has_exception_data_shift;
> +
> + /* Check if short or extended header for a .xdata record should be
> + used. */
> + unsigned header_size = 8;
> + bool single_epilog = false;
> + if ((code_words != 0 || epilogue_count != 0)
code_words is always nonzero (as explained above).
> + && code_words < 32
> + && epilogue_count < 32)
> + {
> + header_size = 4;
> + if (epilogue_count == 1)
This condition still needs more checks (epilogue start index < 32, and I think
also the epilogue needs to be at the end of the function).
> + {
> + single_epilog = true;
> + epilogue_count = 0;
> + }
> + header |= epilogue_count << epilogue_count_shift;
> + header |= code_words << code_words_shift;
> + }
> + else
> + {
> + header |= (uint64_t) epilogue_count << ext_epilogue_count_shift;
> + header |= (uint64_t) code_words << ext_code_words_shift;
> + }
> + header |= single_epilog << single_epilog_shift;
> +
> + md_number_to_chars (frag_more (header_size), header, header_size);
> +
> + if (epilogue_count && !single_epilog)
> + {
> + seh_aarch64_emit_epilogue_scopes (seh_ctx,
> + fragment_offset,
> + first_fragment_scope,
> + last_fragment_scope);
> + }
> +
> + if (code_words)
code_words is always nonzero.
> + seh_aarch64_emit_unwind_codes (seh_ctx, has_phantom_prologue);
> +
> + if (seh_ctx->has_exception_data)
> + {
> + 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);
> + }
> +}
...
> diff --git a/gas/config/obj-coff-seh-aarch64.h b/gas/config/obj-coff-seh-aarch64.h
> new file mode 100644
> index 00000000000..493c69f0527
> --- /dev/null
> +++ b/gas/config/obj-coff-seh-aarch64.h
...
> +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;
These three structs/unions are unused, so should be removed.
Thanks,
Alice
More information about the Binutils
mailing list