diff --git a/include/set-freeres.h b/include/set-freeres.h
index bb1d0a8f72..0f81344e43 100644
--- a/include/set-freeres.h
+++ b/include/set-freeres.h
@@ -78,6 +78,8 @@ extern void __nss_database_freeres (void) attribute_hidden;
 extern int _IO_cleanup (void) attribute_hidden;;
 /* From dlfcn/dlerror.c */
 extern void __libc_dlerror_result_free (void) attribute_hidden;
+/* From libc.so, arch specific.  */
+extern void ARCH_THREAD_FREERES (void) attribute_hidden;
 
 /* From either libc.so or libpthread.so  */
 extern void __libpthread_freeres (void) attribute_hidden;
diff --git a/malloc/thread-freeres.c b/malloc/thread-freeres.c
index c7ec9f99e9..5ed5df1a3f 100644
--- a/malloc/thread-freeres.c
+++ b/malloc/thread-freeres.c
@@ -22,6 +22,14 @@
 #include <shlib-compat.h>
 #include <tls-internal.h>
 
+/* Define empty function if no arch-specific clean-up
+   function has been defined.  */
+#ifndef ARCH_THREAD_FREERES
+void __always_inline
+__libc_arch_thread_freeres (void) {}
+#define ARCH_THREAD_FREERES __libc_arch_thread_freeres
+#endif
+
 /* Thread shutdown function.  Note that this function must be called
    for threads during shutdown for correctness reasons.  Unlike
    __libc_freeres, skipping calls to it is not a valid optimization.
@@ -29,6 +37,7 @@
 void
 __libc_thread_freeres (void)
 {
+  call_function_static_weak (ARCH_THREAD_FREERES);
 #if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_32)
   __rpc_thread_destroy ();
 #endif
diff --git a/sysdeps/unix/sysv/linux/aarch64/makecontext.c b/sysdeps/unix/sysv/linux/aarch64/makecontext.c
index 99ba5b3f7c..f43fc45c76 100644
--- a/sysdeps/unix/sysv/linux/aarch64/makecontext.c
+++ b/sysdeps/unix/sysv/linux/aarch64/makecontext.c
@@ -20,7 +20,9 @@
 #include <sysdep.h>
 #include <stdarg.h>
 #include <stdint.h>
+#include <stdlib.h>
 #include <ucontext.h>
+#include <sys/mman.h>
 
 #define GCS_MAGIC 0x47435300
 
@@ -29,6 +31,47 @@ static struct _aarch64_ctx *extension (void *p)
   return p;
 }
 
+struct gcs_list {
+  struct gcs_list *next;
+  void *base;
+  size_t size;
+};
+
+static __thread struct gcs_list *gcs_list_head = NULL;
+
+static void
+record_gcs (void *base, size_t size)
+{
+  struct gcs_list *p = malloc (sizeof *p);
+  if (p == NULL)
+    abort ();
+  p->base = base;
+  p->size = size;
+  p->next = gcs_list_head;
+  gcs_list_head = p;
+}
+
+static void
+free_gcs_list (void)
+{
+  for (;;)
+    {
+      struct gcs_list *p = gcs_list_head;
+      if (p == NULL)
+	break;
+      gcs_list_head = p->next;
+      __munmap (p->base, p->size);
+      free (p);
+    }
+}
+
+/* Called during thread shutdown to free resources.  */
+void
+__libc_aarch64_thread_freeres (void)
+{
+  free_gcs_list ();
+}
+
 #ifndef __NR_map_shadow_stack
 # define __NR_map_shadow_stack 453
 #endif
@@ -58,6 +101,9 @@ alloc_makecontext_gcs (size_t stack_size)
   if (base == (void *) -1)
     /* ENOSYS, bad size or OOM.  */
     abort ();
+
+  record_gcs (base, size);
+
   uint64_t *gcsp = (uint64_t *) ((char *) base + size);
   /* Skip end of GCS token.  */
   gcsp--;
@@ -69,6 +115,25 @@ alloc_makecontext_gcs (size_t stack_size)
   return gcsp + 1;
 }
 
+void
+__free_makecontext_gcs (void *gcs)
+{
+  struct gcs_list *p = gcs_list_head;
+  struct gcs_list **q = &gcs_list_head;
+  for (;;)
+    {
+      if (p == NULL)
+	abort ();
+      if (gcs == p->base + p->size - 8)
+	break;
+      q = &p->next;
+      p = p->next;
+    }
+  *q = p->next;
+  __munmap (p->base, p->size);
+  free (p);
+}
+
 /* makecontext sets up a stack and the registers for the
    user context.  The stack looks like this:
 
diff --git a/sysdeps/unix/sysv/linux/aarch64/setcontext.S b/sysdeps/unix/sysv/linux/aarch64/setcontext.S
index 695fc5b9b5..f926a5dd84 100644
--- a/sysdeps/unix/sysv/linux/aarch64/setcontext.S
+++ b/sysdeps/unix/sysv/linux/aarch64/setcontext.S
@@ -34,6 +34,9 @@
 	.text
 
 ENTRY (__setcontext)
+	/* If x10 is set then old GCS is freed.  */
+	mov	x10, 0
+__setcontext_internal:
 	PTR_ARG (0)
 	/* Save a copy of UCP.  */
 	mov	x9, x0
@@ -145,7 +148,8 @@ ENTRY (__setcontext)
 	ldr	x3, [x2, #oGCSPR]
 	MRS_GCSPR (x2)
 	mov	x4, x3
-	/* x2: GCSPR now.  x3, x4: target GCSPR.  x5, x6: tmp regs.  */
+	mov	x1, x2
+	/* x1, x2: GCSPR now.  x3, x4: target GCSPR.  x5, x6: tmp regs.  */
 L(gcs_scan):
 	cmp	x2, x4
 	b.eq	L(gcs_pop)
@@ -162,10 +166,18 @@ L(gcs_switch):
 	GCSSS2 (xzr)
 L(gcs_pop):
 	cmp	x2, x3
-	b.eq	L(gcs_done)
+	b.eq	L(gcs_free_old)
 	GCSPOPM (xzr)
 	add	x2, x2, 8
 	b	L(gcs_pop)
+L(gcs_free_old):
+	cbz	x10, L(gcs_done)
+	mov	x28, x0
+	mov	x0, x1
+	bl	__free_makecontext_gcs
+	mov	x0, x28
+	ldp	x28, x29, [x0, oX0 + 28 * SZREG]
+	ldr     x30,      [x0, oX0 + 30 * SZREG]
 L(gcs_done):
 
 2:
@@ -186,6 +198,7 @@ ENTRY (__startcontext)
 	cfi_undefined (x30)
 	blr	x20
 	mov	x0, x19
-	cbnz	x0, __setcontext
+	mov	x10, 1
+	cbnz	x0, __setcontext_internal
 1:	b       HIDDEN_JUMPTARGET (exit)
 END (__startcontext)
diff --git a/sysdeps/unix/sysv/linux/aarch64/sysdep.h b/sysdeps/unix/sysv/linux/aarch64/sysdep.h
index b813805931..e2fc7e21a6 100644
--- a/sysdeps/unix/sysv/linux/aarch64/sysdep.h
+++ b/sysdeps/unix/sysv/linux/aarch64/sysdep.h
@@ -29,8 +29,12 @@
 
 #include <tls.h>
 
-/* In order to get __set_errno() definition in INLINE_SYSCALL.  */
 #ifndef __ASSEMBLER__
+/* Thread cleanup function.  */
+#define ARCH_THREAD_FREERES __libc_aarch64_thread_freeres
+void __libc_aarch64_thread_freeres (void) attribute_hidden;
+
+/* In order to get __set_errno() definition in INLINE_SYSCALL.  */
 #include <errno.h>
 #endif
 
