]> git.proxmox.com Git - ceph.git/blob - ceph/src/seastar/src/core/exception_hacks.cc
import 15.2.0 Octopus source
[ceph.git] / ceph / src / seastar / src / core / exception_hacks.cc
1 /*
2 * This file is open source software, licensed to you under the terms
3 * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
4 * distributed with this work for additional information regarding copyright
5 * ownership. You may not use this file except in compliance with the License.
6 *
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing,
12 * software distributed under the License is distributed on an
13 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 * KIND, either express or implied. See the License for the
15 * specific language governing permissions and limitations
16 * under the License.
17 */
18 /*
19 * Copyright (C) 2017 ScyllaDB
20 */
21
22 // The purpose of the hacks here is to workaround C++ exception scalability problem
23 // with gcc and glibc. For the best result gcc-7 is required.
24 //
25 // To summarize all the locks we have now and their purpose:
26 // 1. There is a lock in _Unwind_Find_FDE (libgcc) that protects
27 // list of "Frame description entries" registered with __register_frame*
28 // functions. The catch is that dynamically linked binary do not do that,
29 // so all that it protects is checking that a certain list is empty.
30 // This lock no longer relevant in gcc-7 since there is a patch there
31 // that checks that the list is empty outside of the lock and it will be
32 // always true for us.
33 // 2. The lock in dl_iterate_phdr (glibc) that protects loaded object
34 // list against runtime object loading/unloading.
35 //
36 // To get rid of the first lock using gcc-7 is required.
37 //
38 // To get rid of the second one we can use the fact that we do not
39 // load/unload objects dynamically (at least for now). To do that we
40 // can mirror all elf header information in seastar and provide our
41 // own dl_iterate_phdr symbol which uses this mirror without locking.
42 //
43 // Unfortunately there is another gotcha in this approach: dl_iterate_phdr
44 // supplied by glibc never calls more then one callback simultaneously as an
45 // unintended consequences of the lock there, but unfortunately libgcc relies
46 // on that to maintain small cache of translations. The access to the cache is
47 // not protected by any lock since up until now only one callback could have
48 // run at a time. But luckily libgcc cannot use the cache if older version
49 // of dl_phdr_info is provided to the callback because the older version
50 // did not have an indication that loaded object list may have changed,
51 // so libgcc does not know when cache should be invalidated and disables it
52 // entirely. By calling the callback with old version of dl_phdr_info from
53 // our dl_iterate_phdr we can effectively make libgcc callback thread safe.
54
55 #include <link.h>
56 #include <dlfcn.h>
57 #include <assert.h>
58 #include <vector>
59 #include <cstddef>
60 #include <seastar/core/exception_hacks.hh>
61 #include <seastar/core/reactor.hh>
62 #include <seastar/util/backtrace.hh>
63
64 namespace seastar {
65 using dl_iterate_fn = int (*) (int (*callback) (struct dl_phdr_info *info, size_t size, void *data), void *data);
66
67 [[gnu::no_sanitize_address]]
68 static dl_iterate_fn dl_iterate_phdr_org() {
69 static dl_iterate_fn org = [] {
70 auto org = (dl_iterate_fn)dlsym (RTLD_NEXT, "dl_iterate_phdr");
71 assert(org);
72 return org;
73 }();
74 return org;
75 }
76
77 // phdrs_cache has to remain valid until very late in the process
78 // life, and that time is not a mirror image of when it is first used.
79 // Given that, we avoid a static constructor/destructor pair and just
80 // never destroy it.
81 static std::vector<dl_phdr_info> *phdrs_cache = nullptr;
82
83 void init_phdr_cache() {
84 // Fill out elf header cache for access without locking.
85 // This assumes no dynamic object loading/unloading after this point
86 phdrs_cache = new std::vector<dl_phdr_info>();
87 dl_iterate_phdr_org()([] (struct dl_phdr_info *info, size_t size, void *data) {
88 phdrs_cache->push_back(*info);
89 return 0;
90 }, nullptr);
91 }
92
93 #ifndef NO_EXCEPTION_INTERCEPT
94 seastar::logger exception_logger("exception");
95
96 void log_exception_trace() noexcept {
97 static thread_local bool nested = false;
98 if (!nested && exception_logger.is_enabled(log_level::trace)) {
99 nested = true;
100 exception_logger.trace("Throw exception at:\n{}", current_backtrace());
101 nested = false;
102 }
103 }
104 #else
105 void log_exception_trace() noexcept {}
106 #endif
107
108 }
109
110 extern "C"
111 [[gnu::visibility("default")]]
112 [[gnu::used]]
113 [[gnu::no_sanitize_address]]
114 int dl_iterate_phdr(int (*callback) (struct dl_phdr_info *info, size_t size, void *data), void *data) {
115 if (!seastar::local_engine || !seastar::phdrs_cache) {
116 // Cache is not yet populated, pass through to original function
117 return seastar::dl_iterate_phdr_org()(callback, data);
118 }
119 int r = 0;
120 for (auto h : *seastar::phdrs_cache) {
121 // Pass dl_phdr_info size that does not include dlpi_adds and dlpi_subs.
122 // This forces libgcc to disable caching which is not thread safe and
123 // requires dl_iterate_phdr to serialize calls to callback. Since we do
124 // not serialize here we have to disable caching.
125 r = callback(&h, offsetof(dl_phdr_info, dlpi_adds), data);
126 if (r) {
127 break;
128 }
129 }
130 return r;
131 }
132
133 #ifndef NO_EXCEPTION_INTERCEPT
134 extern "C"
135 [[gnu::visibility("default")]]
136 [[gnu::used]]
137 int _Unwind_RaiseException(struct _Unwind_Exception *h) {
138 using throw_fn = int (*)(void *);
139 static throw_fn org = nullptr;
140
141 if (!org) {
142 org = (throw_fn)dlsym (RTLD_NEXT, "_Unwind_RaiseException");
143 }
144 if (seastar::local_engine) {
145 seastar::log_exception_trace();
146 seastar::engine()._cxx_exceptions++;
147 }
148 return org(h);
149 }
150 #endif