1 | /* Dynamic loading of the libgcc unwinder. |
2 | Copyright (C) 2021-2023 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 | #include <pointer_guard.h> |
27 | |
28 | /* Statically allocate the object, so that we do not have to deal with |
29 | malloc failure. __libc_unwind_link_get must not fail if libgcc_s |
30 | has already been loaded by other means. */ |
31 | static struct unwind_link global; |
32 | |
33 | /* dlopen handle. Also used for the double-checked locking idiom. */ |
34 | static void *global_libgcc_handle; |
35 | |
36 | /* We cannot use __libc_once because the pthread_once implementation |
37 | may depend on unwinding. */ |
38 | __libc_lock_define (static, lock); |
39 | |
40 | struct unwind_link * |
41 | __libc_unwind_link_get (void) |
42 | { |
43 | /* Double-checked locking idiom. Synchronizes with the release MO |
44 | store at the end of this function. */ |
45 | if (atomic_load_acquire (&global_libgcc_handle) != NULL) |
46 | return &global; |
47 | |
48 | /* Initialize a copy of the data, so that we do not need about |
49 | unlocking in case the dynamic loader somehow triggers |
50 | unwinding. */ |
51 | void *local_libgcc_handle = __libc_dlopen (LIBGCC_S_SO); |
52 | if (local_libgcc_handle == NULL) |
53 | { |
54 | __libc_lock_unlock (lock); |
55 | return NULL; |
56 | } |
57 | |
58 | struct unwind_link local; |
59 | local.ptr__Unwind_Backtrace |
60 | = __libc_dlsym (local_libgcc_handle, "_Unwind_Backtrace" ); |
61 | local.ptr__Unwind_ForcedUnwind |
62 | = __libc_dlsym (local_libgcc_handle, "_Unwind_ForcedUnwind" ); |
63 | local.ptr__Unwind_GetCFA |
64 | = __libc_dlsym (local_libgcc_handle, "_Unwind_GetCFA" ); |
65 | #if UNWIND_LINK_GETIP |
66 | local.ptr__Unwind_GetIP |
67 | = __libc_dlsym (local_libgcc_handle, "_Unwind_GetIP" ); |
68 | #endif |
69 | local.ptr__Unwind_Resume |
70 | = __libc_dlsym (local_libgcc_handle, "_Unwind_Resume" ); |
71 | #if UNWIND_LINK_FRAME_STATE_FOR |
72 | local.ptr___frame_state_for |
73 | = __libc_dlsym (local_libgcc_handle, "__frame_state_for" ); |
74 | #endif |
75 | local.ptr_personality |
76 | = __libc_dlsym (local_libgcc_handle, "__gcc_personality_v0" ); |
77 | UNWIND_LINK_EXTRA_INIT |
78 | |
79 | /* If a symbol is missing, libgcc_s has somehow been corrupted. */ |
80 | assert (local.ptr__Unwind_Backtrace != NULL); |
81 | assert (local.ptr__Unwind_ForcedUnwind != NULL); |
82 | assert (local.ptr__Unwind_GetCFA != NULL); |
83 | #if UNWIND_LINK_GETIP |
84 | assert (local.ptr__Unwind_GetIP != NULL); |
85 | #endif |
86 | assert (local.ptr__Unwind_Resume != NULL); |
87 | assert (local.ptr_personality != NULL); |
88 | |
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 | |
101 | __libc_lock_lock (lock); |
102 | if (atomic_load_relaxed (&global_libgcc_handle) != NULL) |
103 | /* This thread lost the race. Clean up. */ |
104 | __libc_dlclose (local_libgcc_handle); |
105 | else |
106 | { |
107 | global = local; |
108 | |
109 | /* Completes the double-checked locking idiom. */ |
110 | atomic_store_release (&global_libgcc_handle, local_libgcc_handle); |
111 | } |
112 | |
113 | __libc_lock_unlock (lock); |
114 | return &global; |
115 | } |
116 | libc_hidden_def (__libc_unwind_link_get) |
117 | |
118 | void |
119 | __libc_unwind_link_after_fork (void) |
120 | { |
121 | if (__libc_lock_trylock (lock) == 0) |
122 | /* The lock was not acquired during the fork. This covers both |
123 | the initialized and uninitialized case. */ |
124 | __libc_lock_unlock (lock); |
125 | else |
126 | { |
127 | /* Initialization was in progress in another thread. |
128 | Reinitialize the lock. */ |
129 | __libc_lock_init (lock); |
130 | global_libgcc_handle = NULL; |
131 | } |
132 | } |
133 | |
134 | void |
135 | __libc_unwind_link_freeres (void) |
136 | { |
137 | if (global_libgcc_handle != NULL) |
138 | { |
139 | __libc_dlclose (global_libgcc_handle ); |
140 | global_libgcc_handle = NULL; |
141 | } |
142 | } |
143 | |
144 | #endif /* SHARED */ |
145 | |