diff --git a/elf/Makefile b/elf/Makefile
index b46dee1c16..33f88bae96 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -303,6 +303,7 @@ tests := \
   tst-array4 \
   tst-array5 \
   tst-auxv \
+  tst-decorate-maps \
   tst-dl-hash \
   tst-leaks1 \
   tst-stringtable \
@@ -2980,3 +2981,5 @@ LDFLAGS-tst-dlclose-lazy-mod1.so = -Wl,-z,lazy,--no-as-needed
 $(objpfx)tst-dlclose-lazy-mod1.so: $(objpfx)tst-dlclose-lazy-mod2.so
 $(objpfx)tst-dlclose-lazy.out: \
   $(objpfx)tst-dlclose-lazy-mod1.so $(objpfx)tst-dlclose-lazy-mod2.so
+
+$(objpfx)tst-decorate-maps: $(shared-thread-library)
diff --git a/elf/tst-decorate-maps.c b/elf/tst-decorate-maps.c
new file mode 100644
index 0000000000..b40a0e9b49
--- /dev/null
+++ b/elf/tst-decorate-maps.c
@@ -0,0 +1,160 @@
+/* Check the VMA name decoration.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/test-driver.h>
+#include <support/xstdio.h>
+#include <support/xthread.h>
+#include <support/xunistd.h>
+#include <sys/mman.h>
+
+#ifndef MAP_STACK
+# define MAP_STACK 0
+#endif
+
+static pthread_barrier_t b;
+
+static void *
+tf (void *closure)
+{
+  /* Wait the thread startup, so thread stack is allocated.  */
+  xpthread_barrier_wait (&b);
+
+  /* Wait the test to read the process mapping.  */
+  xpthread_barrier_wait (&b);
+
+  return NULL;
+}
+
+struct proc_maps_t
+{
+  int n_def_threads;
+  int n_user_threads;
+};
+
+static struct proc_maps_t
+read_proc_maps (void)
+{
+  if (test_verbose)
+    printf ("=== print process %jd memory mapping ===\n",
+	    (intmax_t) getpid ());
+  struct proc_maps_t r = { 0 };
+
+  FILE *f = xfopen ("/proc/self/maps", "r");
+  char *line = NULL;
+  size_t line_len = 0;
+  while (xgetline (&line, &line_len, f))
+    {
+      if (test_verbose)
+	printf ("%s", line);
+      if (strstr (line, "[anon: glibc: pthread stack:") != NULL)
+	r.n_def_threads++;
+      else if (strstr (line, "[anon: glibc: pthread user stack:") != NULL)
+	r.n_user_threads++;
+    }
+  free (line);
+  xfclose (f);
+
+  if (test_verbose)
+    printf ("===\n");
+  return r;
+}
+
+static void
+do_test_threads (bool set_guard)
+{
+  enum
+    {
+      num_def_threads  = 8,
+      num_user_threads = 2,
+      num_threads = num_def_threads + num_user_threads,
+    };
+
+  xpthread_barrier_init (&b, NULL, num_threads + 1);
+
+  pthread_t thr[num_threads];
+  {
+    int i = 0;
+    for (; i < num_threads - num_user_threads; i++)
+      {
+	pthread_attr_t attr;
+	xpthread_attr_init (&attr);
+	/* The guard page is not annotated.  */
+	if (!set_guard)
+	  xpthread_attr_setguardsize (&attr, 0);
+	thr[i] = xpthread_create (&attr, tf, NULL);
+	xpthread_attr_destroy (&attr);
+      }
+    for (; i < num_threads; i++)
+      {
+	pthread_attr_t attr;
+	xpthread_attr_init (&attr);
+	size_t stacksize = support_small_thread_stack_size ();
+	void *stack = xmmap (0,
+			     stacksize,
+			     PROT_READ | PROT_WRITE,
+			     MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK,
+			     -1);
+	xpthread_attr_setstack (&attr, stack, stacksize);
+	if (!set_guard)
+	  xpthread_attr_setguardsize (&attr, 0);
+	thr[i] = xpthread_create (&attr, tf, NULL);
+	xpthread_attr_destroy (&attr);
+      }
+  }
+
+  /* Wait all threads to finshed statup and stack allocation.  */
+  xpthread_barrier_wait (&b);
+
+  {
+    struct proc_maps_t r = read_proc_maps ();
+    TEST_COMPARE (r.n_def_threads, num_def_threads);
+    TEST_COMPARE (r.n_user_threads, num_user_threads);
+  }
+
+  /* Let the threads finish.  */
+  xpthread_barrier_wait (&b);
+
+  for (int i = 0; i < num_threads; i++)
+    xpthread_join (thr[i]);
+
+  {
+    struct proc_maps_t r = read_proc_maps ();
+    TEST_COMPARE (r.n_def_threads, 0);
+    TEST_COMPARE (r.n_user_threads, 0);
+  }
+}
+
+static int
+do_test (void)
+{
+  support_need_proc ("Reads /proc/self/maps to get stack names.");
+
+  if (!support_set_vma_name_supported ())
+    FAIL_UNSUPPORTED ("kernel does not support PR_SET_VMA_ANON_NAME");
+
+  do_test_threads (false);
+  do_test_threads (true);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c
index f9d8cdfd08..97d0efec10 100644
--- a/nptl/allocatestack.c
+++ b/nptl/allocatestack.c
@@ -33,6 +33,8 @@
 #include <nptl-stack.h>
 #include <libc-lock.h>
 #include <tls-internal.h>
+#include <intprops.h>
+#include <setvmaname.h>
 
 /* Default alignment of stack.  */
 #ifndef STACK_ALIGN
@@ -577,3 +579,41 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
 
   return 0;
 }
+
+/* Maximum supported name from initial kernel support, not exported
+   by user API.  */
+#define ANON_VMA_NAME_MAX_LEN 80
+
+#define SET_STACK_NAME(__prefix, __stack, __stacksize, __tid)		\
+  ({									\
+     char __stack_name[sizeof (__prefix) +				\
+		       INT_BUFSIZE_BOUND (unsigned int)];		\
+     _Static_assert (sizeof __stack_name <= ANON_VMA_NAME_MAX_LEN,	\
+		     "VMA name size larger than maximum supported");	\
+     __snprintf (__stack_name, sizeof (__stack_name), __prefix "%u",	\
+		 (unsigned int) __tid);					\
+     __set_vma_name (__stack, __stacksize, __stack_name);		\
+   })
+
+/* Add or remove an associated name to the PD VMA stack.  */
+static void
+name_stack_maps (struct pthread *pd, bool set)
+{
+#if _STACK_GROWS_DOWN && !defined(NEED_SEPARATE_REGISTER_STACK)
+  void *stack = pd->stackblock + pd->guardsize;
+#else
+  void *stack = pd->stackblock;
+#endif
+  size_t stacksize = pd->stackblock_size - pd->guardsize;
+
+  if (!set)
+    __set_vma_name (stack, stacksize, NULL);
+  else
+    {
+      unsigned int tid = pd->tid;
+      if (pd->user_stack)
+	SET_STACK_NAME (" glibc: pthread user stack: ", stack, stacksize, tid);
+      else
+	SET_STACK_NAME (" glibc: pthread stack: ", stack, stacksize, tid);
+    }
+}
diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
index 6a41d50109..63cb684f04 100644
--- a/nptl/pthread_create.c
+++ b/nptl/pthread_create.c
@@ -369,6 +369,9 @@ start_thread (void *arg)
   /* Initialize pointers to locale data.  */
   __ctype_init ();
 
+  /* Name the thread stack if kernel supports it.  */
+  name_stack_maps (pd, true);
+
   /* Register rseq TLS to the kernel.  */
   {
     bool do_rseq = THREAD_GETMEM (pd, flags) & ATTR_FLAG_DO_RSEQ;
@@ -571,6 +574,9 @@ start_thread (void *arg)
     /* Free the TCB.  */
     __nptl_free_tcb (pd);
 
+  /* Remove the associated name from the thread stack.  */
+  name_stack_maps (pd, false);
+
 out:
   /* We cannot call '_exit' here.  '_exit' will terminate the process.
 
