[PATCH v1 4/4] aarch64 gas: track and free dynamically allocated register aliases

Matthieu Longo matthieu.longo@arm.com
Tue Feb 24 16:26:17 GMT 2026


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.

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