[PATCH v4 5/7] Write SEH records to pdata/xdata
Evgeny Karpov
evgeny.karpov@arm.com
Mon Feb 2 15:55:30 GMT 2026
On Fri, Oct 17, 2025, Jan Beulich wrote:
> On 15.08.2025 00:50, Evgeny Karpov wrote:
> > --- a/gas/config/obj-coff-seh.c
> > +++ b/gas/config/obj-coff-seh.c
> > @@ -1256,7 +1256,86 @@ seh_x64_write_prologue_data (const seh_context *c)
> > }
> > }
> > }
> > +#endif
> > +
> > +#if defined (COFFAARCH64)
> > +static void
> > +seh_aarch64_emit_epilog_scopes (const seh_context *seh_ctx,
> > + uint64_t fragment_offset,
> > + uint32_t prolog_size,
> > + unsigned int first_fragment_scope,
> > + unsigned int last_fragment_scope,
> > + bool has_phantom_prolog)
> > +{
> > + int32_t start_index_offset = 0;
>
> With this being a signed type, ...
>
> > + const
> > + seh_aarch64_epilogue_scope *scopes = seh_ctx->aarch64_ctx.epilogue_scopes;
> > + if (first_fragment_scope < seh_ctx->aarch64_ctx.epilogue_scopes_count)
> > + start_index_offset = scopes[first_fragment_scope].epilogue_start_index
> > + - prolog_size;
> > + if (has_phantom_prolog)
> > + start_index_offset -= 1;
>
> ... the variable can end up holding -1 here. What does ...
>
> > + for (unsigned int i = first_fragment_scope; i < last_fragment_scope; ++i)
> > + {
> > + seh_aarch64_epilogue_scope scope;
> > + scope = seh_ctx->aarch64_ctx.epilogue_scopes[i];
> > + scope.epilogue_start_offset_reduced = (scope.epilogue_start_offset
> > + - fragment_offset) >> 2;
> > + scope.epilogue_start_index -= start_index_offset;
>
> ... this mean here? You'd then _increment_ the destination by one. If all
> of this is intentional, some commentary would help. Otherwise please use
> an unsigned type and make clear why there cannot be any underflow.
>
> Also - why is that variable of a fixed width type? Plain (signed or unsigned)
> int would apparently do, wouldn't it?
Yes, it makes sense to change to unsigned and add a condition for the decrement on
has_phantom_prolog.
> > + uint32_t scope_code;
> > + memcpy (&scope_code, &scope, sizeof (scope_code));
> > + out_four (scope_code);
> > + }
> > +}
> > +
> > +static void
> > +seh_aarch64_emit_unwind_codes (const seh_context *seh_ctx,
> > + uint32_t prolog_size,
> > + uint32_t first_epilog_index,
> > + uint32_t last_epilog_index,
> > + bool has_phantom_prolog)
> > +{
> > + uint32_t total_byte_count = 0;
> > +
> > + if (has_phantom_prolog)
> > + {
> > + ++total_byte_count;
> > + md_number_to_chars (frag_more (1), AARCH64_UNOP_ENDC, 1);
> > + }
> >
> > + uint32_t unwind_bytes_offset = 0;
> > + for (int i = 0; i < (int)seh_ctx->aarch64_ctx.unwind_codes_count; ++i)
>
> Why the cast? Why isn't i of an appropriate unsigned type?
Ok.
> > + {
> > + const seh_aarch64_unwind_code *code = seh_ctx->aarch64_ctx.unwind_codes
> > + + i;
> > + const int 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. */
> > + int required_padding = (4 - total_byte_count % 4) % 4;
>
> And again - why plain int? And what are the literal 4-s expressing?
Yes, it makes sense to change.
> > + if (required_padding)
> > + {
> > + const uint32_t nop_chain = 0xe3e3e3e3;
>
> Please at least have a comment here, such that grep-ing for AARCH64_UNOP_NOP
> will hit this line.
A comment will be added.
> > @@ -1352,6 +1431,213 @@ seh_x64_write_function_xdata (seh_context *c)
> > }
> > #endif
> >
> > +#if defined (COFFAARCH64)
> > +/* Write out the xdata information for one function (aarch64). */
> > +static void
> > +seh_aarch64_write_function_xdata (seh_context *seh_ctx)
>
> For the function parameter and elsewhere - can you please see about applying
> const to pointed-to types wherever possible?
It was following the same semantics as x64, however it will be changed during
the refactoring.
> > +{
> > + if (!seh_ctx->aarch64_ctx.unwind_codes_byte_count)
> > + return;
> > +
> > + /* Set 4-byte alignment. */
> > + frag_align (2, 0, 0);
>
> This may be related to the 4-s above - can they be tied together by using
> some #define-s?
It will be refactored.
> > + 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;
> > + 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));
> > + return;
> > + }
> > +
> > + uintptr_t func_size = offset;
> > +
> > + const uint32_t max_frag_size = ((1 << 18) - 1) << 2;
>
> Where does this boundary come from?
https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170#large-functions
The large functions should be split into fragments smaller than 1MB.
> > + uintptr_t fragment_offset = 0;
> > + bool is_fragmented_function = func_size > max_frag_size;
> > +
> > + /* [first_fragment_scope, last_fragment_scope). */
> > + unsigned int first_fragment_scope = 0;
> > + unsigned int last_fragment_scope = 0;
> > + uint32_t prolog_size = 0;
>
> The initializer is pointless here. Also you want to be consistent with
> variable definitions: Either at the beginning of a scope, or really
> immediately ahead of when they're first used.
It will be moved closer to the place of use.
> > + uint32_t prolog_insruction_count = 0;
>
> Nit: typo (missing 't' as it looks). But maybe prolog_insn_count would
> be slightly better anyway?
>
> For both of them (and many others elsewhere) also again: Why fixed-
> width?
Yes, just unsigned works here.
> > + for (unsigned int i = 0; i < seh_ctx->aarch64_ctx.unwind_codes_count; ++i)
> > + {
> > + if (seh_ctx->aarch64_ctx.unwind_codes[i].type == end)
>
> This use of "end" makes pretty clear that those enumerators lack some
> disambiguating prefix.
A prefix will be added.
> > + {
> > + prolog_insruction_count = i + 1;
> > + break;
> > + }
> > + }
> > +
> > + if (seh_ctx->aarch64_ctx.epilogue_scopes_count)
> > + prolog_size = seh_ctx->aarch64_ctx.epilogue_scopes[0].epilogue_start_index;
> > + else
> > + prolog_size = seh_ctx->aarch64_ctx.unwind_codes_byte_count;
> > +
> > + seh_aarch64_func_fragment *fragment;
> > + fragment = &seh_ctx->aarch64_ctx.func_fragment;
>
> Why not right as initializer of the variable? (Apparently I overlooked
> similar anomalies already further up.)
The reason is the 80 characters limit per line. Any other preferable style?
> > + 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;
> > +
> > + bool is_first_frag = fragment_offset == 0;
> > + bool is_last_frag = (fragment_offset + frag_size) == func_size;
> > +
> > + if (!is_fragmented_function)
> > + last_fragment_scope = seh_ctx->aarch64_ctx.epilogue_scopes_count;
> > + else
> > + {
> > + first_fragment_scope = last_fragment_scope;
> > + for (unsigned int i = first_fragment_scope;
> > + i < seh_ctx->aarch64_ctx.epilogue_scopes_count; ++i)
> > + {
> > + const seh_aarch64_epilogue_scope *scope;
> > + scope = seh_ctx->aarch64_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->aarch64_ctx.xdata_header;
> > + const
> > + seh_aarch64_epilogue_scope *scopes =
> > seh_ctx->aarch64_ctx.epilogue_scopes;
> > +
> > + header->func_length = frag_size >> 2;
>
> Another such magic number.
This is how the function length is encoded.
https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170#xdata-records
> > + 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;
> > +
> > + uint32_t first_epilog_index = 0;
> > + uint32_t 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->aarch64_ctx.epilogue_scopes_count)
> > + last_epilog_index = seh_ctx->aarch64_ctx.unwind_codes_byte_count;
> > + else
> > + {
> > + scope = scopes + last_fragment_scope;
> > + last_epilog_index = scope->epilogue_start_index;
> > + }
> > + }
> > +
> > + uint32_t unwind_bytes = 0;
> > + if (is_first_frag || is_last_frag)
> > + unwind_bytes += prolog_size;
>
> Why += when = would do?
The code is easily extendable with '+=' if needed.
> > + if (header->ext_epilogue_count)
> > + unwind_bytes += last_epilog_index - first_epilog_index;
> > +
> > + if (is_fragmented_function && is_last_frag && unwind_bytes)
> > + {
> > + unwind_bytes += 1;
> > + ++header->ext_epilogue_count;
> > + }
>
> Something like this (and perhaps quite a few other places) could really
> do with comments. Otherwise how is someone else supposed to be knowing
> why things are the way they are?
Extra comments will be added.
> > + header->ext_code_words = (unwind_bytes + 3) / 4;
>
> Nit: Stray blank (and again magic numbers).
Another encoding in xdata. A comment will help.
> > + 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->aarch64_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 (is_fragmented_function && is_last_frag)
> > + 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;
> > + }
> > + }
> > + out_four (seh_ctx->aarch64_ctx.xdata_header_value);
> > + }
> > +
> > + bool has_phantom_prolog = is_fragmented_function && is_last_frag;
> > + 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 (is_fragmented_function && is_last_frag)
> > + {
> > + uint32_t epilog_start_offset;
> > + epilog_start_offset = frag_size - prolog_insruction_count * 4;
> > + md_number_to_chars (frag_more (4),
> > + (1 << 22) | (epilog_start_offset >> 2), 4);
>
> And yet several more magic numbers.
Ok, it will be explained in comments.
> > @@ -1441,6 +1728,24 @@ write_function_pdata (seh_context *c)
> > switch_pdata (c->code_seg);
> >
> > #if defined (COFFAARCH64)
> > + if (c->aarch64_ctx.unwind_codes_byte_count)
> > + {
> > + seh_aarch64_func_fragment *fragment = &c->aarch64_ctx.func_fragment;
> > + while (fragment)
> > + {
> > + exp.X_op = O_symbol_rva;
> > + exp.X_add_number = fragment->offset;
> > + exp.X_add_symbol = c->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;
> > + }
> > + }
>
> Wouldn't this better live ...
>
> > #else
> > switch (seh_get_target_kind ())
> > {
>
> ... in the body of this switch()? Later on more #ifdef-ary could (imo should)
> then be added, to make sure we don't build dead code.
It will disappear after refactoring.
Regards,
Evgeny
More information about the Binutils
mailing list