[PATCH v9 1/1] aarch64: Implement Structured Exception Handling (SEH) on AArch64
Alice Carlotti
alice.carlotti@arm.com
Mon Jun 22 14:50:01 GMT 2026
On Wed, Jun 17, 2026 at 10:39:47AM +0200, Evgeny Karpov wrote:
...
> +/* Write out the xdata information for one function. */
> +static void
> +seh_aarch64_write_function_xdata (struct seh_aarch64_context *seh_ctx)
Oops, I forgot to write my comments on this functions.
In general, I think this function ought handle function fragmention, but the
encoding details (including choosing between compact or extended header fields)
should be handled in seh_aarch64_emit_xdata_record. This separation of
concerns should help with readability.
I think there are also several corner cases that are mishandled in this code,
but I've been struggling to work out what's going on well enough to say that
particular pieces are right or wrong. I'll try to point any specific issues I
see, but I'd appreciate if you could also see whether you can do anything more
to improve the structure of this code.
> +{
> + 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, based on
> + "Microsoft ARM64 exception handling, large functions documentation". */
> + 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. */
See previous review comments about how this implementation doesn't currently
support (or need to handle) fragmenting due to too many unwind codes or too
many epilogue scopes.
> + 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;
It would be clearer to use separate variables here, and only combine them into
a single header value within seh_aarch64_emit_xdata_record. We can't do the
type-punning anyway, so there's no benefit to putting the values into a struct.
This also avoids any ambiguity about whether the short or extended fields
should be used for computations in this function.
> + 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;
The is_last_frag part looks wrong - for a typical function layout, I'd expect
only the first fragment to have a real prologue, and the rest to have a phantom
prologue.
> + 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;
> + }
> + }
> + }
This code is mostly selecting between different header formats, so I think it
should be part of seh_aarch64_emit_xdata_record.
> +
> + /* 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);
> +}
Thanks,
Alice
More information about the Binutils
mailing list