[PATCH v1 4/4] aarch64 gas: track and free dynamically allocated register aliases
Alice Carlotti
alice.carlotti@arm.com
Tue Mar 3 12:56:58 GMT 2026
On Tue, Feb 24, 2026 at 04:26:17PM +0000, Matthieu Longo wrote:
> Register aliases are stored in the aarch64_reg_hsh hash table. When
> aliases are added via .req, a new alias entry is dynamically allocated
> and inserted into the table alongside statically-allocated entries.
> When aliases are removed via .unreq, the current implementation cannot
> reliably determine which objects must be freed and which must not.
>
> As a result, unregistering predefined aliases leads to a segmentation
> fault, since the associated memory resides in a read-only statically-
> allocated section. Besides, if user-defined aliases are not explicitly
> unregistered via .unreq, their associated memory leaks. This issue was
> detected by LeakSanitizer (see the stack trace below).
>
> This patch introduces a dynamic-allocation tracker to explicitly record
> dynamically-allocated objects. The tracker is implemented as an htab_t
> specialized for pointer keys. Allocated memory is registered with
> DYN_ALLOC_TRACK(), and can later be identified with DYN_ALLOC_TRACKED()
> and safely freed using DYN_ALLOC_UNTRACK() when an alias is unregistered.
> Any remaining tracked allocations (i.e. still-registered user-defined
> aliases) are released at md_end() to ensure a clean exit.
I don't like this approach. I think in this case it's much simpler just to
insist that all aliases use dynamically allocated data - this only requires
copying the data for the 8 predefined aliases at startup. See my proposed
patches:
[PATCH 2/6] aarch64: Fix .unreq of predefined aliases
https://sourceware.org/pipermail/binutils/2026-March/148408.html
[RFC 6/6] aarch64 gas: Free reg alias entries before exit
https://sourceware.org/pipermail/binutils/2026-March/148412.html
I'd appreciate your view on those alternative patches.
Thanks,
Alice
>
> With this new approach, predefined and user-defined aliases can be
> reliably distinguished, allowing predefined aliases to be safely
> unregistered without triggering invalid memory accesses.
>
> The patch also updates existing .req and .unreq test cases to cover
> the following scenarios:
> - registering and unregistering user-defined aliases
> - unregistering predefined aliases while still emitting a warning
> - reporting an error when attempting to unregister built-in registers
>
> ==ERROR: LeakSanitizer: detected memory leaks
> Direct leak of 80 byte(s) in 5 object(s) allocated from:
> #1 in xmalloc ./libiberty/xmalloc.c:149
> #2 in insert_reg_alias ./gas/config/tc-aarch64.c:1622
> #3 in create_register_alias ./gas/config/tc-aarch64.c:1680
> #4 in md_assemble ./gas/config/tc-aarch64.c:8862
> ---
> gas/config/tc-aarch64.c | 98 ++++++++++++++++---
> gas/testsuite/gas/aarch64/register_aliases.d | 4 +
> gas/testsuite/gas/aarch64/register_aliases.s | 10 +-
> .../gas/aarch64/register_aliases_invalid.l | 7 ++
> .../gas/aarch64/register_aliases_invalid.s | 21 +++-
> 5 files changed, 122 insertions(+), 18 deletions(-)
>
> diff --git a/gas/config/tc-aarch64.c b/gas/config/tc-aarch64.c
> index b1f85456ef4..0b05564c3a7 100644
> --- a/gas/config/tc-aarch64.c
> +++ b/gas/config/tc-aarch64.c
> @@ -549,6 +549,28 @@ set_expected_reglist_error (aarch64_reg_type type, const reg_entry *reg)
> /* Instructions take 4 bytes in the object file. */
> #define INSN_SIZE 4
>
> +/* Dynamic-allocation tracker.
> + Some dynamically-allocated objects are stored alongside statically-allocated
> + ones inside the same collection. This mixture makes it impossible to reliably
> + determine which object should be freed and which should not.
> + To address this, the dynamic-allocation tracker records the address of such
> + objects at allocation time via DYN_ALLOC_TRACK(). A dynamically-allocated
> + object can later be identified with DYN_ALLOC_TRACKED() by looking up its
> + address in the tracker and, if found, safely freed using DYN_ALLOC_UNTRACK().
> + Objects that are intended to live for the entire lifetime of GAS may remain
> + tracked; they will be automatically deallocated in md_end() before exit to
> + avoid memory leaks. */
> +static htab_t dyn_alloc_tracker;
> +
> +#define DYN_ALLOC_TRACK(ptr) \
> + htab_insert (dyn_alloc_tracker, (void *) ptr, 0)
> +
> +#define DYN_ALLOC_UNTRACK(ptr) \
> + htab_remove_elt (dyn_alloc_tracker, ptr)
> +
> +#define DYN_ALLOC_TRACKED(ptr) \
> + (htab_find (dyn_alloc_tracker, ptr) != NULL)
> +
> static htab_t aarch64_ops_hsh;
> static htab_t aarch64_cond_hsh;
> static htab_t aarch64_shift_hsh;
> @@ -1599,7 +1621,7 @@ parse_simd_vector_with_bit_index (char **ccp, struct vector_type_el *typeinfo)
> /* Directives: register aliases. */
>
> static reg_entry *
> -insert_reg_alias (char *str, int number, aarch64_reg_type type)
> +insert_reg_alias (const char *str, int number, aarch64_reg_type type)
> {
> reg_entry *new;
> const char *name;
> @@ -1620,6 +1642,14 @@ insert_reg_alias (char *str, int number, aarch64_reg_type type)
>
> name = xstrdup (str);
> new = XNEW (reg_entry);
> + /* Note: checking the builtin attribute is insufficient to determine whether
> + an alias is dynamically or statically allocated. Some register aliases are
> + pre-registered yet can still be unregistered. Tracking dynamic allocations
> + allows user-defined aliases to be distinguished from pre-registered ones,
> + and ensures that only dynamically-allocated resources are attempted to be
> + deallocated. */
> + DYN_ALLOC_TRACK (name);
> + DYN_ALLOC_TRACK (new);
>
> new->name = name;
> new->number = number;
> @@ -1749,39 +1779,62 @@ s_unreq (int a ATTRIBUTE_UNUSED)
> name);
> else
> {
> - char *p;
> - char *nbuf;
> + /* Determine whether the memory was dynamically allocated. If so,
> + reuse the original allocation and avoid allocating a new buffer.
> + Otherwise, allocate a new buffer, since the original memory is
> + likely located in the .rodata section (a read-only section) whereas
> + the buffer has to be mutable. */
> + bool dyn_alloc_tracked = DYN_ALLOC_TRACKED (reg->name);
> + char *nbuf
> + = (dyn_alloc_tracked
> + ? (char *) reg->name
> + : xstrdup (reg->name));
>
> str_hash_delete (aarch64_reg_hsh, name);
> - free ((char *) reg->name);
> - free (reg);
> + if (dyn_alloc_tracked)
> + DYN_ALLOC_UNTRACK (reg);
>
> - /* Also locate the all upper case and all lower case versions.
> + /* Locate the all upper case and all lower case versions.
> Do not complain if we cannot find one or the other as it
> was probably deleted above. */
> -
> - nbuf = strdup (name);
> - for (p = nbuf; *p; p++)
> + for (char *p = nbuf; *p; p++)
> *p = TOUPPER (*p);
> reg = str_hash_find (aarch64_reg_hsh, nbuf);
> if (reg)
> {
> str_hash_delete (aarch64_reg_hsh, nbuf);
> - free ((char *) reg->name);
> - free (reg);
> + if (dyn_alloc_tracked)
> + {
> + gas_assert (DYN_ALLOC_TRACKED (reg)
> + && DYN_ALLOC_TRACKED (reg->name));
> + DYN_ALLOC_UNTRACK (reg->name);
> + DYN_ALLOC_UNTRACK (reg);
> + }
> }
>
> - for (p = nbuf; *p; p++)
> + for (char *p = nbuf; *p; p++)
> *p = TOLOWER (*p);
> reg = str_hash_find (aarch64_reg_hsh, nbuf);
> if (reg)
> {
> str_hash_delete (aarch64_reg_hsh, nbuf);
> - free ((char *) reg->name);
> - free (reg);
> + if (dyn_alloc_tracked)
> + {
> + gas_assert (DYN_ALLOC_TRACKED (reg)
> + && DYN_ALLOC_TRACKED (reg->name));
> + DYN_ALLOC_UNTRACK (reg->name);
> + DYN_ALLOC_UNTRACK (reg);
> + }
> }
>
> - free (nbuf);
> + if (dyn_alloc_tracked)
> + DYN_ALLOC_UNTRACK (nbuf);
> + else
> + {
> + as_warn (_("unregistering predefined register alias '%s'"),
> + name);
> + free (nbuf);
> + }
> }
> }
>
> @@ -10567,6 +10620,13 @@ dyn_str_htab_create (void)
> free_dyn_string_key_tuple, notes_calloc, NULL);
> }
>
> +static htab_t
> +dyn_alloc_htab_create (void)
> +{
> + return htab_create_alloc (16, htab_hash_pointer, htab_eq_pointer,
> + free, notes_calloc, NULL);
> +}
> +
> /* MD interface: Initialization. */
>
> void
> @@ -10575,6 +10635,11 @@ md_begin (void)
> unsigned mach;
> unsigned int i;
>
> + /* Initialize the dynamic-allocation tracker. Any memory registered from this
> + point onward will live until md_end(), unless it is explicitly removed
> + using DYN_ALLOC_UNTRACK(). */
> + dyn_alloc_tracker = dyn_alloc_htab_create ();
> +
> /* Each hash table value is a dynamically allocated node pointing to the
> opcode associated with its key. Nodes with the same key are chained. */
> aarch64_ops_hsh = instruction_htab_create ();
> @@ -10803,6 +10868,9 @@ aarch64_md_end (void)
> htab_delete (aarch64_pldop_hsh);
> htab_delete (aarch64_hint_opt_hsh);
>
> + /* Free all the dynamically-allocated objects registered in the tracker. */
> + htab_delete (dyn_alloc_tracker);
> +
> free (mcpu_cpu_opt);
> }
>
> diff --git a/gas/testsuite/gas/aarch64/register_aliases.d b/gas/testsuite/gas/aarch64/register_aliases.d
> index 8d614b47606..8f8510731e1 100644
> --- a/gas/testsuite/gas/aarch64/register_aliases.d
> +++ b/gas/testsuite/gas/aarch64/register_aliases.d
> @@ -1,3 +1,7 @@
> +#warning: [^:]*: Assembler messages:
> +#warning: [^:]*:19: Warning: unregistering predefined register alias 'lr'
> +#warning: [^:]*:20: Warning: unregistering predefined register alias 'fp'
> +
> #objdump: -dr
>
> .*: file format .*
> diff --git a/gas/testsuite/gas/aarch64/register_aliases.s b/gas/testsuite/gas/aarch64/register_aliases.s
> index 856be5699ce..a122915d359 100644
> --- a/gas/testsuite/gas/aarch64/register_aliases.s
> +++ b/gas/testsuite/gas/aarch64/register_aliases.s
> @@ -1,4 +1,4 @@
> - # test register aliases.
> + # Use register aliases.
> lr .req x30
> fp .req x29
> ip0 .req x16
> @@ -10,3 +10,11 @@
> str IP0, [fp]
> ldr IP1, [fp]
> str zero, [x0]
> +
> + # Register and unregister user-defined aliases
> + foo .req x29
> + .unreq foo
> +
> + # Unregister predefined aliases
> + .unreq lr
> + .unreq fp
> diff --git a/gas/testsuite/gas/aarch64/register_aliases_invalid.l b/gas/testsuite/gas/aarch64/register_aliases_invalid.l
> index 6350049df74..394d8299638 100644
> --- a/gas/testsuite/gas/aarch64/register_aliases_invalid.l
> +++ b/gas/testsuite/gas/aarch64/register_aliases_invalid.l
> @@ -1,3 +1,10 @@
> .*:
> .*: Error: unknown mnemonic `lr\.req' -- `lr\.req x30'
> .*: Error: unknown mnemonic `lr\.a' -- `lr\.a .req x30'
> +.*: Warning: ignoring attempt to undefine built-in register 'w0'
> +.*: Warning: ignoring attempt to undefine built-in register 'W0'
> +.*: Warning: ignoring attempt to undefine built-in register 'x0'
> +.*: Warning: ignoring attempt to undefine built-in register 'X0'
> +.*: Warning: ignoring attempt to undefine built-in register 'z0'
> +.*: Error: unknown register alias 'foo'
> +.*: Error: unknown register alias 'foo'
> diff --git a/gas/testsuite/gas/aarch64/register_aliases_invalid.s b/gas/testsuite/gas/aarch64/register_aliases_invalid.s
> index 2df2eaab4d6..156e821fd18 100644
> --- a/gas/testsuite/gas/aarch64/register_aliases_invalid.s
> +++ b/gas/testsuite/gas/aarch64/register_aliases_invalid.s
> @@ -1,2 +1,19 @@
> -lr.req x30
> -lr.a .req x30
> + lr.req x30
> + lr.a .req x30
> +
> + # Try to unregister builtin registers.
> + .unreq w0
> + .unreq W0
> + .unreq x0
> + .unreq X0
> + .unreq z0
> +
> + # Try to unregister unknown alias.
> + .unreq foo
> +
> + # Check that unregistering via the capitalized alias also unregisters
> + # the lowercase alias.
> + foo .req x29
> + .unreq FOO
> + # This second unregistration should fail.
> + .unreq foo
> --
> 2.53.0
>
More information about the Binutils
mailing list