diff mbox series

[v2,3/7] nptl: Decorate thread stack on pthread_create

Message ID 20231028195559.390407-4-adhemerval.zanella@linaro.org
State Superseded
Headers show
Series Add a tunable to decorate anonymous memory maps | expand

Commit Message

Adhemerval Zanella Netto Oct. 28, 2023, 7:55 p.m. UTC
Linux 4.5 removed thread stack annotations due to the complexity of
computing them [1], and Linux added PR_SET_VMA_ANON_NAME on 5.17
as a way to name anonymous virtual memory areas.

This patch adds decoration on the stack created and used by
pthread_create, for glibc crated thread stack the /proc/self/maps will
now show:

  [anon: glibc: pthread stack: <tid>]

And for user-provided stacks:

  [anon: glibc: pthread user stack: <tid>]

The guard page is not decorated, and the mapping name is cleared when
the thread finishes its execution (so the cached stack does not have any
name associated).

Co-authored-by: Ian Rogers <irogers@google.com>

Checked on x86_64-linux-gnu aarch64 aarch64-linux-gnu.

[1] https://github.com/torvalds/linux/commit/65376df582174ffcec9e6471bf5b0dd79ba05e4a
---
 elf/Makefile            |   3 +
 elf/tst-decorate-maps.c | 160 ++++++++++++++++++++++++++++++++++++++++
 nptl/allocatestack.c    |  40 ++++++++++
 nptl/pthread_create.c   |   6 ++
 4 files changed, 209 insertions(+)
 create mode 100644 elf/tst-decorate-maps.c

Comments

DJ Delorie Nov. 1, 2023, 2:04 a.m. UTC | #1
One spelling fix.
One question but doesn't block

LGTM.
Reviewed-by: DJ Delorie <dj@redhat.com>

Adhemerval Zanella <adhemerval.zanella@linaro.org> writes:

> diff --git a/elf/Makefile b/elf/Makefile
> +  tst-decorate-maps \

Ok.

> +
> +$(objpfx)tst-decorate-maps: $(shared-thread-library)

Ok.

> diff --git a/elf/tst-decorate-maps.c b/elf/tst-decorate-maps.c
> +/* 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>

Ok.

> +#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 mapiping.  */

Spelling: "mapping"

> +  xpthread_barrier_wait (&b);
> +
> +  return NULL;
> +}

Ok.

> +struct proc_maps_t
> +{
> +  int n_def_threads;
> +  int n_user_threads;
> +};

Ok.

> +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 };

Misleading, but OK.  Should be { 0, 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;
> +}

Ok.

> +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,
> +    };

10 threads total, ok.

> +  xpthread_barrier_init (&b, NULL, num_threads + 1);

Ok.

> +  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);
> +      }

Ok.

> +    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);
> +      }
> +  }

Ok.

> +  /* 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);
> +  }

Ok.

> +  /* 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);
> +  }
> +}

Ok.

> +static int
> +do_test (void)
> +{
> +  support_need_proc ("Reads /proc/self/maps to get stack names.");
> +
> +  if (!support_set_vma_name ())
> +    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>

Ok.

> diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c
> +#include <intprops.h>
> +#include <setvmaname.h>

Ok.

> +/* 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);		\
> +   })

Ok.

> +/* 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;

Do we have any stack-grows-up systems to test this on?

> +#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);
> +    }
> +}

Ok.

> diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
>    /* Initialize pointers to locale data.  */
>    __ctype_init ();
>  
> +  /* Name the thread stack if kernel supports it.  */
> +  name_stack_maps (pd, true);
> +

Ok.

> @@ -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.

Ok.
diff mbox series

Patch

diff --git a/elf/Makefile b/elf/Makefile
index 9176cbf1e3..a82590703c 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 \
@@ -3018,3 +3019,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..bbb7972094
--- /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 mapiping.  */
+  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 ())
+    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.