about summary refs log tree commit diff
path: root/misc/unwind-link.c
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2021-03-01 15:56:36 +0100
committerFlorian Weimer <fweimer@redhat.com>2021-03-01 15:58:01 +0100
commit9fc813e1a37d2e2d5e85a97d5ac4fc1c15d839fb (patch)
tree62e5002b97acab6f776476c2325a37097693b0ea /misc/unwind-link.c
parent764e9a0334350f52ab6953bef1db97f9b2e89ca5 (diff)
downloadglibc-9fc813e1a37d2e2d5e85a97d5ac4fc1c15d839fb.tar.gz
glibc-9fc813e1a37d2e2d5e85a97d5ac4fc1c15d839fb.tar.xz
glibc-9fc813e1a37d2e2d5e85a97d5ac4fc1c15d839fb.zip
Implement <unwind-link.h> for dynamically loading the libgcc_s unwinder
This will be used to consolidate the libgcc_s access for backtrace
and pthread_cancel.

Unlike the existing backtrace implementations, it provides some
hardening based on pointer mangling.

Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Diffstat (limited to 'misc/unwind-link.c')
-rw-r--r--misc/unwind-link.c145
1 files changed, 145 insertions, 0 deletions
diff --git a/misc/unwind-link.c b/misc/unwind-link.c
new file mode 100644
index 0000000000..ad3d02bf32
--- /dev/null
+++ b/misc/unwind-link.c
@@ -0,0 +1,145 @@
+/* Dynamic loading of the libgcc unwinder.
+   Copyright (C) 2021 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/>.  */
+
+#ifdef SHARED
+
+#include <assert.h>
+#include <dlfcn.h>
+#include <gnu/lib-names.h>
+#include <unwind-link.h>
+#include <libc-lock.h>
+
+/* Statically allocate the object, so that we do not have to deal with
+   malloc failure.  __libc_unwind_link_get must not fail if libgcc_s
+   has already been loaded by other means.  */
+static struct unwind_link global;
+
+/* dlopen handle.  Also used for the double-checked locking idiom.  */
+static void *global_libgcc_handle;
+
+/* We cannot use __libc_once because the pthread_once implementation
+   may depend on unwinding.  */
+__libc_lock_define (static, lock);
+
+struct unwind_link *
+__libc_unwind_link_get (void)
+{
+  /* Double-checked locking idiom.  Synchronizes with the release MO
+     store at the end of this function.  */
+  if (atomic_load_acquire (&global_libgcc_handle) != NULL)
+   return &global;
+
+  /* Initialize a copy of the data, so that we do not need about
+     unlocking in case the dynamic loader somehow triggers
+     unwinding.  */
+  void *local_libgcc_handle = __libc_dlopen (LIBGCC_S_SO);
+  if (local_libgcc_handle == NULL)
+    {
+      __libc_lock_unlock (lock);
+      return NULL;
+    }
+
+  struct unwind_link local;
+  local.ptr__Unwind_Backtrace
+    = __libc_dlsym (local_libgcc_handle, "_Unwind_Backtrace");
+  local.ptr__Unwind_ForcedUnwind
+    = __libc_dlsym (local_libgcc_handle, "_Unwind_ForcedUnwind");
+  local.ptr__Unwind_GetCFA
+    = __libc_dlsym (local_libgcc_handle, "_Unwind_GetCFA");
+#if UNWIND_LINK_GETIP
+  local.ptr__Unwind_GetIP
+    = __libc_dlsym (local_libgcc_handle, "_Unwind_GetIP");
+#endif
+  local.ptr__Unwind_Resume
+    = __libc_dlsym (local_libgcc_handle, "_Unwind_Resume");
+#if UNWIND_LINK_FRAME_STATE_FOR
+  local.ptr___frame_state_for
+    = __libc_dlsym (local_libgcc_handle, "__frame_state_for");
+#endif
+  local.ptr_personality
+    = __libc_dlsym (local_libgcc_handle, "__gcc_personality_v0");
+  UNWIND_LINK_EXTRA_INIT
+
+  /* If a symbol is missing, libgcc_s has somehow been corrupted.  */
+  assert (local.ptr__Unwind_Backtrace != NULL);
+  assert (local.ptr__Unwind_ForcedUnwind != NULL);
+  assert (local.ptr__Unwind_GetCFA != NULL);
+#if UNWIND_LINK_GETIP
+  assert (local.ptr__Unwind_GetIP != NULL);
+#endif
+  assert (local.ptr__Unwind_Resume != NULL);
+  assert (local.ptr_personality != NULL);
+
+#ifdef PTR_MANGLE
+  PTR_MANGLE (local.ptr__Unwind_Backtrace);
+  PTR_MANGLE (local.ptr__Unwind_ForcedUnwind);
+  PTR_MANGLE (local.ptr__Unwind_GetCFA);
+# if UNWIND_LINK_GETIP
+  PTR_MANGLE (local.ptr__Unwind_GetIP);
+# endif
+  PTR_MANGLE (local.ptr__Unwind_Resume);
+# if UNWIND_LINK_FRAME_STATE_FOR
+  PTR_MANGLE (local.ptr___frame_state_for);
+# endif
+  PTR_MANGLE (local.ptr_personality);
+#endif
+
+  __libc_lock_lock (lock);
+  if (atomic_load_relaxed (&global_libgcc_handle) != NULL)
+    /* This thread lost the race.  Clean up.  */
+    __libc_dlclose (local_libgcc_handle);
+  else
+    {
+      global = local;
+
+      /* Completes the double-checked locking idiom.  */
+      atomic_store_release (&global_libgcc_handle, local_libgcc_handle);
+    }
+
+  __libc_lock_unlock (lock);
+  return &global;
+}
+libc_hidden_def (__libc_unwind_link_get)
+
+void
+__libc_unwind_link_after_fork (void)
+{
+  if (__libc_lock_trylock (lock) == 0)
+    /* The lock was not acquired during the fork.  This covers both
+       the initialized and uninitialized case.  */
+    __libc_lock_unlock (lock);
+  else
+    {
+      /* Initialization was in progress in another thread.
+         Reinitialize the lock.  */
+      __libc_lock_init (lock);
+      global_libgcc_handle = NULL;
+    }
+}
+
+void __libc_freeres_fn_section
+__libc_unwind_link_freeres (void)
+{
+  if (global_libgcc_handle != NULL)
+    {
+      __libc_dlclose (global_libgcc_handle );
+      global_libgcc_handle = NULL;
+    }
+}
+
+#endif /* SHARED */