[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