]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
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 { | |
11fdf7f2 TL |
65 | using dl_iterate_fn = int (*) (int (*callback) (struct dl_phdr_info *info, size_t size, void *data), void *data); |
66 | ||
9f95a23c | 67 | [[gnu::no_sanitize_address]] |
11fdf7f2 TL |
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 | ||
9f95a23c TL |
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; | |
11fdf7f2 TL |
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 | |
9f95a23c | 86 | phdrs_cache = new std::vector<dl_phdr_info>(); |
11fdf7f2 | 87 | dl_iterate_phdr_org()([] (struct dl_phdr_info *info, size_t size, void *data) { |
9f95a23c | 88 | phdrs_cache->push_back(*info); |
11fdf7f2 TL |
89 | return 0; |
90 | }, nullptr); | |
91 | } | |
11fdf7f2 TL |
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 | ||
11fdf7f2 TL |
110 | extern "C" |
111 | [[gnu::visibility("default")]] | |
9f95a23c TL |
112 | [[gnu::used]] |
113 | [[gnu::no_sanitize_address]] | |
11fdf7f2 | 114 | int dl_iterate_phdr(int (*callback) (struct dl_phdr_info *info, size_t size, void *data), void *data) { |
9f95a23c | 115 | if (!seastar::local_engine || !seastar::phdrs_cache) { |
11fdf7f2 TL |
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; | |
9f95a23c | 120 | for (auto h : *seastar::phdrs_cache) { |
11fdf7f2 TL |
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 | } | |
11fdf7f2 TL |
132 | |
133 | #ifndef NO_EXCEPTION_INTERCEPT | |
134 | extern "C" | |
135 | [[gnu::visibility("default")]] | |
9f95a23c | 136 | [[gnu::used]] |
11fdf7f2 TL |
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 |