[v2,1/2] Thread-safety improvements for bfd_check_format_matches

Message ID 20240415182328.2754398-2-tom@tromey.com
State New
Headers
Series Fix some races seen by thread sanitizer |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_binutils_build--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_binutils_check--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_binutils_build--master-arm success Testing passed
linaro-tcwg-bot/tcwg_binutils_check--master-arm success Testing passed

Commit Message

Tom Tromey April 15, 2024, 6:22 p.m. UTC
  A gdb bug found that bfd_check_format_matches has some data races when
called from multiple threads.

In particular, it changes the BFD error handler, which is a global.
It also has a local static variable ("in_check_format") that is used
for recursion detection.  And, finally, it may emit warnings to the
per-xvec warning array, which is a global.

This patch removes all the races here.

The first part of patch is to change _bfd_error_handler to directly
handle the needs of bfd_check_format_matches.  This way, the error
handler does not need to be changed.

This change lets us use the new per-thread global
(error_handler_messages, replacing error_handler_bfd) to also remove
the need for in_check_format -- a single variable suffices.

Finally, the global per-xvec array is replaced with a new type that
holds the error messages.  The outermost such type is stack-allocated
in bfd_check_format_matches.

I tested this using the binutils test suite.  I also built gdb with
thread sanitizer and ran the test case that was noted as failing.
Finally, Alan sent me the test file that caused the addition of the
xvec warning code in the first place, and I confirmed that "nm-new"
has the same behavior on this file both before and after this patch.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31264
---
 bfd/bfd.c     | 141 ++++++++++++++++++++++++++++++++++++++++++++------
 bfd/format.c  |  71 ++++++++++++-------------
 bfd/libbfd.h  |  33 ++++++++----
 bfd/targets.c |  58 ---------------------
 4 files changed, 182 insertions(+), 121 deletions(-)
  

Comments

Alan Modra April 16, 2024, 3:13 a.m. UTC | #1
You need this or something like it on top of your patch, otherwise
the first element with PER_XVEC_NO_TARGET won't have targ set (and the
first element will be used for everything).

diff --git a/bfd/bfd.c b/bfd/bfd.c
index 80ce859d874..ace2f67954f 100644
--- a/bfd/bfd.c
+++ b/bfd/bfd.c
@@ -1574,10 +1574,10 @@ INTERNAL
 .  char message[];
 .};
 .
-.{* A list of per_xvec_message objects.  The targ field
-.   indicates which xvec this list holds; PER_XVEC_NO_TARGET
-.   indicates that this entry isn't yet used. The abfd field is
-.   only needed in the root entry of the list.  *}
+.{* A list of per_xvec_message objects.  The targ field indicates
+.   which xvec this list holds; PER_XVEC_NO_TARGET is only set for the
+.   root of the list and indicates that the entry isn't yet used.  The
+.   abfd field is only needed in the root entry of the list.  *}
 .struct per_xvec_messages
 .{
 .  bfd *abfd;
@@ -1600,12 +1600,15 @@ _bfd_per_xvec_warn (struct per_xvec_messages *messages, size_t alloc)
   struct per_xvec_messages *prev = NULL;
   struct per_xvec_messages *iter = messages;
 
-  for (iter = messages; iter != NULL; iter = iter->next)
-    {
-      if (iter->targ == targ || iter->targ == PER_XVEC_NO_TARGET)
-	break;
-      prev = iter;
-    }
+  if (iter->targ == PER_XVEC_NO_TARGET)
+    iter->targ = targ;
+  else
+    for (; iter != NULL; iter = iter->next)
+      {
+	if (iter->targ == targ)
+	  break;
+	prev = iter;
+      }
 
   if (iter == NULL)
     {
@@ -1618,8 +1621,6 @@ _bfd_per_xvec_warn (struct per_xvec_messages *messages, size_t alloc)
       iter->next = NULL;
       prev->next = iter;
     }
-  else if (iter->targ == NULL)
-    iter->targ = targ;
 
   struct per_xvec_message **m = &iter->messages;
   int count = 0;
  
Tom Tromey April 16, 2024, 7:51 p.m. UTC | #2
>>>>> "Alan" == Alan Modra <amodra@gmail.com> writes:

Alan> You need this or something like it on top of your patch, otherwise
Alan> the first element with PER_XVEC_NO_TARGET won't have targ set (and the
Alan> first element will be used for everything).

Sorry about that.
Your change looks correct to me, I'll apply it and re-send.

Tom
  

Patch

diff --git a/bfd/bfd.c b/bfd/bfd.c
index 8fd86f68e6e..80ce859d874 100644
--- a/bfd/bfd.c
+++ b/bfd/bfd.c
@@ -1565,10 +1565,90 @@  err_sprintf (void *stream, const char *fmt, ...)
   return total;
 }
 
-/* Communicate the bfd processed by bfd_check_format_matches to the
-   error handling function error_handler_sprintf.  */
+/*
+INTERNAL
+.{* Cached _bfd_check_format messages are put in this.  *}
+.struct per_xvec_message
+.{
+.  struct per_xvec_message *next;
+.  char message[];
+.};
+.
+.{* A list of per_xvec_message objects.  The targ field
+.   indicates which xvec this list holds; PER_XVEC_NO_TARGET
+.   indicates that this entry isn't yet used. The abfd field is
+.   only needed in the root entry of the list.  *}
+.struct per_xvec_messages
+.{
+.  bfd *abfd;
+.  const bfd_target *targ;
+.  struct per_xvec_message *messages;
+.  struct per_xvec_messages *next;
+.};
+.
+.#define PER_XVEC_NO_TARGET ((const bfd_target *) -1)
+*/
+
+/* Helper function to find or allocate the correct per-xvec object
+   when emitting a message.  */
+
+static struct per_xvec_message *
+_bfd_per_xvec_warn (struct per_xvec_messages *messages, size_t alloc)
+{
+  const bfd_target *targ = messages->abfd->xvec;
+
+  struct per_xvec_messages *prev = NULL;
+  struct per_xvec_messages *iter = messages;
+
+  for (iter = messages; iter != NULL; iter = iter->next)
+    {
+      if (iter->targ == targ || iter->targ == PER_XVEC_NO_TARGET)
+	break;
+      prev = iter;
+    }
+
+  if (iter == NULL)
+    {
+      iter = bfd_malloc (sizeof (*iter));
+      if (iter == NULL)
+	return NULL;
+      iter->abfd = messages->abfd;
+      iter->targ = targ;
+      iter->messages = NULL;
+      iter->next = NULL;
+      prev->next = iter;
+    }
+  else if (iter->targ == NULL)
+    iter->targ = targ;
+
+  struct per_xvec_message **m = &iter->messages;
+  int count = 0;
+  while (*m)
+    {
+      m = &(*m)->next;
+      count++;
+    }
+  /* Anti-fuzzer measure.  Don't cache more than 5 messages.  */
+  if (count < 5)
+    {
+      *m = bfd_malloc (sizeof (**m) + alloc);
+      if (*m != NULL)
+	(*m)->next = NULL;
+    }
+  return *m;
+}
 
-static bfd *error_handler_bfd;
+/* Communicate the error-message container processed by
+   bfd_check_format_matches to the error handling function
+   error_handler_sprintf.  When non-NULL, _bfd_error_handler will call
+   error_handler_sprintf; when NULL, _bfd_error_internal will be used
+   instead.  */
+
+static TLS struct per_xvec_messages *error_handler_messages;
+
+/* A special value for error_handler_messages that indicates that the
+   error should simply be ignored.  */
+#define IGNORE_ERROR_MESSAGES ((struct per_xvec_messages *) -1)
 
 /* An error handler that prints to a string, then dups that string to
    a per-xvec cache.  */
@@ -1585,12 +1665,12 @@  error_handler_sprintf (const char *fmt, va_list ap)
   _bfd_print (err_sprintf, &error_stream, fmt, ap);
 
   size_t len = error_stream.ptr - error_buf;
-  struct per_xvec_message **warn
-    = _bfd_per_xvec_warn (error_handler_bfd->xvec, len + 1);
-  if (*warn)
+  struct per_xvec_message *warn
+    = _bfd_per_xvec_warn (error_handler_messages, len + 1);
+  if (warn)
     {
-      memcpy ((*warn)->message, error_buf, len);
-      (*warn)->message[len] = 0;
+      memcpy (warn->message, error_buf, len);
+      warn->message[len] = 0;
     }
 }
 
@@ -1628,7 +1708,14 @@  _bfd_error_handler (const char *fmt, ...)
   va_list ap;
 
   va_start (ap, fmt);
-  _bfd_error_internal (fmt, ap);
+  if (error_handler_messages == IGNORE_ERROR_MESSAGES)
+    {
+      /* Nothing.  */
+    }
+  else if (error_handler_messages != NULL)
+    error_handler_sprintf (fmt, ap);
+  else
+    _bfd_error_internal (fmt, ap);
   va_end (ap);
 }
 
@@ -1659,18 +1746,42 @@  INTERNAL_FUNCTION
 	_bfd_set_error_handler_caching
 
 SYNOPSIS
-	bfd_error_handler_type _bfd_set_error_handler_caching (bfd *);
+	struct per_xvec_messages *_bfd_set_error_handler_caching (struct per_xvec_messages *);
 
 DESCRIPTION
 	Set the BFD error handler function to one that stores messages
-	to the per_xvec_warn array.  Returns the previous function.
+	to the per_xvec_messages object.  Returns the previous object
+	to which messages are stored.  Note that two sequential calls
+	to this with a non-NULL argument will cause output to be
+	dropped, rather than gathered.
 */
 
-bfd_error_handler_type
-_bfd_set_error_handler_caching (bfd *abfd)
+struct per_xvec_messages *
+_bfd_set_error_handler_caching (struct per_xvec_messages *messages)
+{
+  struct per_xvec_messages *old = error_handler_messages;
+  if (old == NULL)
+    error_handler_messages = messages;
+  else
+    error_handler_messages = IGNORE_ERROR_MESSAGES;
+  return old;
+}
+
+/*
+INTERNAL_FUNCTION
+	_bfd_restore_error_handler_caching
+
+SYNOPSIS
+	void _bfd_restore_error_handler_caching (struct per_xvec_messages *);
+
+DESCRIPTION
+	Reset the BFD error handler object to an earlier value.
+*/
+
+void
+_bfd_restore_error_handler_caching (struct per_xvec_messages *old)
 {
-  error_handler_bfd = abfd;
-  return bfd_set_error_handler (error_handler_sprintf);
+  error_handler_messages = old;
 }
 
 /*
diff --git a/bfd/format.c b/bfd/format.c
index 6d95683acb5..2a700bab557 100644
--- a/bfd/format.c
+++ b/bfd/format.c
@@ -272,10 +272,34 @@  clear_warnmsg (struct per_xvec_message **list)
   *list = NULL;
 }
 
+/* Free all the storage in LIST.  Note that the first element of LIST
+   is special and is assumed to be stack-allocated.  TARG is used for
+   re-issuing warning messages.  If TARG is PER_XVEC_NO_TARGET, then
+   it acts like a sort of wildcard -- messages are only reissued if
+   they are all associated with a single BFD target, regardless of
+   which one it is.  If TARG is anything else, then only messages
+   associated with TARG are emitted.  */
+
 static void
-null_error_handler (const char *fmt ATTRIBUTE_UNUSED,
-		    va_list ap ATTRIBUTE_UNUSED)
+print_and_clear_messages (struct per_xvec_messages *list,
+			  const bfd_target *targ)
 {
+  struct per_xvec_messages *iter = list;
+
+  if (targ == PER_XVEC_NO_TARGET && list->next == NULL)
+    print_warnmsg (&list->messages);
+
+  while (iter != NULL)
+    {
+      struct per_xvec_messages *next = iter->next;
+
+      if (iter->targ == targ)
+	print_warnmsg (&iter->messages);
+      clear_warnmsg (&iter->messages);
+      if (iter != list)
+	free (iter);
+      iter = next;
+    }
 }
 
 /* This a copy of lto_section defined in GCC (lto-streamer.h).  */
@@ -357,8 +381,8 @@  bfd_check_format_matches (bfd *abfd, bfd_format format, char ***matching)
   unsigned int initial_section_id = _bfd_section_id;
   struct bfd_preserve preserve, preserve_match;
   bfd_cleanup cleanup = NULL;
-  bfd_error_handler_type orig_error_handler;
-  static int in_check_format;
+  struct per_xvec_messages messages = { abfd, PER_XVEC_NO_TARGET, NULL, NULL };
+  struct per_xvec_messages *orig_messages;
 
   if (matching != NULL)
     *matching = NULL;
@@ -392,11 +416,7 @@  bfd_check_format_matches (bfd *abfd, bfd_format format, char ***matching)
 
   /* Don't report errors on recursive calls checking the first element
      of an archive.  */
-  if (in_check_format)
-    orig_error_handler = bfd_set_error_handler (null_error_handler);
-  else
-    orig_error_handler = _bfd_set_error_handler_caching (abfd);
-  ++in_check_format;
+  orig_messages = _bfd_set_error_handler_caching (&messages);
 
   preserve_match.marker = NULL;
   if (!bfd_preserve_save (abfd, &preserve, NULL))
@@ -638,15 +658,9 @@  bfd_check_format_matches (bfd *abfd, bfd_format format, char ***matching)
       if (preserve_match.marker != NULL)
 	bfd_preserve_finish (abfd, &preserve_match);
       bfd_preserve_finish (abfd, &preserve);
-      bfd_set_error_handler (orig_error_handler);
+      _bfd_restore_error_handler_caching (orig_messages);
 
-      struct per_xvec_message **list = _bfd_per_xvec_warn (abfd->xvec, 0);
-      if (*list)
-	print_warnmsg (list);
-      list = _bfd_per_xvec_warn (NULL, 0);
-      for (size_t i = 0; i < _bfd_target_vector_entries + 1; i++)
-	clear_warnmsg (list++);
-      --in_check_format;
+      print_and_clear_messages (&messages, abfd->xvec);
 
       bfd_set_lto_type (abfd);
 
@@ -692,27 +706,8 @@  bfd_check_format_matches (bfd *abfd, bfd_format format, char ***matching)
   if (preserve_match.marker != NULL)
     bfd_preserve_finish (abfd, &preserve_match);
   bfd_preserve_restore (abfd, &preserve);
-  bfd_set_error_handler (orig_error_handler);
-  struct per_xvec_message **list = _bfd_per_xvec_warn (NULL, 0);
-  struct per_xvec_message **one = NULL;
-  for (size_t i = 0; i < _bfd_target_vector_entries + 1; i++)
-    {
-      if (list[i])
-	{
-	  if (!one)
-	    one = list + i;
-	  else
-	    {
-	      one = NULL;
-	      break;
-	    }
-	}
-    }
-  if (one)
-    print_warnmsg (one);
-  for (size_t i = 0; i < _bfd_target_vector_entries + 1; i++)
-    clear_warnmsg (list++);
-  --in_check_format;
+  _bfd_restore_error_handler_caching (orig_messages);
+  print_and_clear_messages (&messages, PER_XVEC_NO_TARGET);
   return false;
 }
 
diff --git a/bfd/libbfd.h b/bfd/libbfd.h
index 5f5ad2daace..4f0acc48b49 100644
--- a/bfd/libbfd.h
+++ b/bfd/libbfd.h
@@ -963,7 +963,29 @@  void _bfd_clear_error_data (void) ATTRIBUTE_HIDDEN;
 
 char *bfd_asprintf (const char *fmt, ...) ATTRIBUTE_HIDDEN;
 
-bfd_error_handler_type _bfd_set_error_handler_caching (bfd *) ATTRIBUTE_HIDDEN;
+/* Cached _bfd_check_format messages are put in this.  */
+struct per_xvec_message
+{
+  struct per_xvec_message *next;
+  char message[];
+};
+
+/* A list of per_xvec_message objects.  The targ field
+   indicates which xvec this list holds; PER_XVEC_NO_TARGET
+   indicates that this entry isn't yet used. The abfd field is
+   only needed in the root entry of the list.  */
+struct per_xvec_messages
+{
+  bfd *abfd;
+  const bfd_target *targ;
+  struct per_xvec_message *messages;
+  struct per_xvec_messages *next;
+};
+
+#define PER_XVEC_NO_TARGET ((const bfd_target *) -1)
+struct per_xvec_messages *_bfd_set_error_handler_caching (struct per_xvec_messages *) ATTRIBUTE_HIDDEN;
+
+void _bfd_restore_error_handler_caching (struct per_xvec_messages *) ATTRIBUTE_HIDDEN;
 
 const char *_bfd_get_error_program_name (void) ATTRIBUTE_HIDDEN;
 
@@ -3708,15 +3730,6 @@  bool _bfd_write_stab_strings (bfd *, struct stab_info *) ATTRIBUTE_HIDDEN;
 bfd_vma _bfd_stab_section_offset (asection *, void *, bfd_vma) ATTRIBUTE_HIDDEN;
 
 /* Extracted from targets.c.  */
-/* Cached _bfd_check_format messages are put in this.  */
-struct per_xvec_message
-{
-  struct per_xvec_message *next;
-  char message[];
-};
-
-struct per_xvec_message **_bfd_per_xvec_warn (const bfd_target *, size_t) ATTRIBUTE_HIDDEN;
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/bfd/targets.c b/bfd/targets.c
index 532b6465a61..0d5d73ba462 100644
--- a/bfd/targets.c
+++ b/bfd/targets.c
@@ -1454,9 +1454,6 @@  const bfd_target *const *const bfd_associated_vector = _bfd_associated_vector;
    number of entries that the array could possibly need.  */
 const size_t _bfd_target_vector_entries = ARRAY_SIZE (_bfd_target_vector);
 
-/* A place to stash a warning from _bfd_check_format.  */
-static struct per_xvec_message *per_xvec_warn[ARRAY_SIZE (_bfd_target_vector)
-					      + 1];
 
 /* This array maps configuration triplets onto BFD vectors.  */
 
@@ -1476,61 +1473,6 @@  static const struct targmatch bfd_target_match[] = {
   { NULL, NULL }
 };
 
-/*
-INTERNAL
-.{* Cached _bfd_check_format messages are put in this.  *}
-.struct per_xvec_message
-.{
-.  struct per_xvec_message *next;
-.  char message[];
-.};
-.
-INTERNAL_FUNCTION
-	_bfd_per_xvec_warn
-
-SYNOPSIS
-	struct per_xvec_message **_bfd_per_xvec_warn (const bfd_target *, size_t);
-
-DESCRIPTION
-	Return a location for the given target xvec to use for
-	warnings specific to that target.  If TARG is NULL, returns
-	the array of per_xvec_message pointers, otherwise if ALLOC is
-	zero, returns a pointer to a pointer to the list of messages
-	for TARG, otherwise (both TARG and ALLOC non-zero), allocates
-	a new 	per_xvec_message with space for a string of ALLOC
-	bytes and returns a pointer to a pointer to it.  May return a
-	pointer to a NULL pointer on allocation failure.
-*/
-
-struct per_xvec_message **
-_bfd_per_xvec_warn (const bfd_target *targ, size_t alloc)
-{
-  size_t idx;
-
-  if (!targ)
-    return per_xvec_warn;
-  for (idx = 0; idx < ARRAY_SIZE (_bfd_target_vector); idx++)
-    if (_bfd_target_vector[idx] == targ)
-      break;
-  struct per_xvec_message **m = per_xvec_warn + idx;
-  if (!alloc)
-    return m;
-  int count = 0;
-  while (*m)
-    {
-      m = &(*m)->next;
-      count++;
-    }
-  /* Anti-fuzzer measure.  Don't cache more than 5 messages.  */
-  if (count < 5)
-    {
-      *m = bfd_malloc (sizeof (**m) + alloc);
-      if (*m != NULL)
-	(*m)->next = NULL;
-    }
-  return m;
-}
-
 /* Find a target vector, given a name or configuration triplet.  */
 
 static const bfd_target *