1 /* Dynamic loading of the libgcc unwinder.
2    Copyright (C) 2021-2022 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4 
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9 
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, see
17    <https://www.gnu.org/licenses/>.  */
18 
19 #ifdef SHARED
20 
21 #include <assert.h>
22 #include <dlfcn.h>
23 #include <gnu/lib-names.h>
24 #include <unwind-link.h>
25 #include <libc-lock.h>
26 
27 /* Statically allocate the object, so that we do not have to deal with
28    malloc failure.  __libc_unwind_link_get must not fail if libgcc_s
29    has already been loaded by other means.  */
30 static struct unwind_link global;
31 
32 /* dlopen handle.  Also used for the double-checked locking idiom.  */
33 static void *global_libgcc_handle;
34 
35 /* We cannot use __libc_once because the pthread_once implementation
36    may depend on unwinding.  */
37 __libc_lock_define (static, lock);
38 
39 struct unwind_link *
__libc_unwind_link_get(void)40 __libc_unwind_link_get (void)
41 {
42   /* Double-checked locking idiom.  Synchronizes with the release MO
43      store at the end of this function.  */
44   if (atomic_load_acquire (&global_libgcc_handle) != NULL)
45    return &global;
46 
47   /* Initialize a copy of the data, so that we do not need about
48      unlocking in case the dynamic loader somehow triggers
49      unwinding.  */
50   void *local_libgcc_handle = __libc_dlopen (LIBGCC_S_SO);
51   if (local_libgcc_handle == NULL)
52     {
53       __libc_lock_unlock (lock);
54       return NULL;
55     }
56 
57   struct unwind_link local;
58   local.ptr__Unwind_Backtrace
59     = __libc_dlsym (local_libgcc_handle, "_Unwind_Backtrace");
60   local.ptr__Unwind_ForcedUnwind
61     = __libc_dlsym (local_libgcc_handle, "_Unwind_ForcedUnwind");
62   local.ptr__Unwind_GetCFA
63     = __libc_dlsym (local_libgcc_handle, "_Unwind_GetCFA");
64 #if UNWIND_LINK_GETIP
65   local.ptr__Unwind_GetIP
66     = __libc_dlsym (local_libgcc_handle, "_Unwind_GetIP");
67 #endif
68   local.ptr__Unwind_Resume
69     = __libc_dlsym (local_libgcc_handle, "_Unwind_Resume");
70 #if UNWIND_LINK_FRAME_STATE_FOR
71   local.ptr___frame_state_for
72     = __libc_dlsym (local_libgcc_handle, "__frame_state_for");
73 #endif
74   local.ptr_personality
75     = __libc_dlsym (local_libgcc_handle, "__gcc_personality_v0");
76   UNWIND_LINK_EXTRA_INIT
77 
78   /* If a symbol is missing, libgcc_s has somehow been corrupted.  */
79   assert (local.ptr__Unwind_Backtrace != NULL);
80   assert (local.ptr__Unwind_ForcedUnwind != NULL);
81   assert (local.ptr__Unwind_GetCFA != NULL);
82 #if UNWIND_LINK_GETIP
83   assert (local.ptr__Unwind_GetIP != NULL);
84 #endif
85   assert (local.ptr__Unwind_Resume != NULL);
86   assert (local.ptr_personality != NULL);
87 
88 #ifdef PTR_MANGLE
89   PTR_MANGLE (local.ptr__Unwind_Backtrace);
90   PTR_MANGLE (local.ptr__Unwind_ForcedUnwind);
91   PTR_MANGLE (local.ptr__Unwind_GetCFA);
92 # if UNWIND_LINK_GETIP
93   PTR_MANGLE (local.ptr__Unwind_GetIP);
94 # endif
95   PTR_MANGLE (local.ptr__Unwind_Resume);
96 # if UNWIND_LINK_FRAME_STATE_FOR
97   PTR_MANGLE (local.ptr___frame_state_for);
98 # endif
99   PTR_MANGLE (local.ptr_personality);
100 #endif
101 
102   __libc_lock_lock (lock);
103   if (atomic_load_relaxed (&global_libgcc_handle) != NULL)
104     /* This thread lost the race.  Clean up.  */
105     __libc_dlclose (local_libgcc_handle);
106   else
107     {
108       global = local;
109 
110       /* Completes the double-checked locking idiom.  */
111       atomic_store_release (&global_libgcc_handle, local_libgcc_handle);
112     }
113 
114   __libc_lock_unlock (lock);
115   return &global;
116 }
libc_hidden_def(__libc_unwind_link_get)117 libc_hidden_def (__libc_unwind_link_get)
118 
119 void
120 __libc_unwind_link_after_fork (void)
121 {
122   if (__libc_lock_trylock (lock) == 0)
123     /* The lock was not acquired during the fork.  This covers both
124        the initialized and uninitialized case.  */
125     __libc_lock_unlock (lock);
126   else
127     {
128       /* Initialization was in progress in another thread.
129          Reinitialize the lock.  */
130       __libc_lock_init (lock);
131       global_libgcc_handle = NULL;
132     }
133 }
134 
135 void __libc_freeres_fn_section
__libc_unwind_link_freeres(void)136 __libc_unwind_link_freeres (void)
137 {
138   if (global_libgcc_handle != NULL)
139     {
140       __libc_dlclose (global_libgcc_handle );
141       global_libgcc_handle = NULL;
142     }
143 }
144 
145 #endif /* SHARED */
146