96595946f197366ed3ae7704944ce9a4070194db
[folly.git] / folly / fibers / GuardPageAllocator.cpp
1 /*
2  * Copyright 2016 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "GuardPageAllocator.h"
17
18 #ifndef _WIN32
19 #include <dlfcn.h>
20 #endif
21 #include <signal.h>
22
23 #include <mutex>
24
25 #include <folly/Singleton.h>
26 #include <folly/SpinLock.h>
27 #include <folly/Synchronized.h>
28 #include <folly/portability/SysMman.h>
29 #include <folly/portability/Unistd.h>
30
31 #include <glog/logging.h>
32
33 namespace folly {
34 namespace fibers {
35
36 /**
37  * Each stack with a guard page creates two memory mappings.
38  * Since this is a limited resource, we don't want to create too many of these.
39  *
40  * The upper bound on total number of mappings created
41  * is kNumGuarded * kMaxInUse.
42  */
43
44 /**
45  * Number of guarded stacks per allocator instance
46  */
47 constexpr size_t kNumGuarded = 100;
48
49 /**
50  * Maximum number of allocator instances with guarded stacks enabled
51  */
52 constexpr size_t kMaxInUse = 100;
53
54 /**
55  * A cache for kNumGuarded stacks of a given size
56  *
57  * Thread safe.
58  */
59 class StackCache {
60  public:
61   explicit StackCache(size_t stackSize) : allocSize_(allocSize(stackSize)) {
62     auto p = ::mmap(
63         nullptr,
64         allocSize_ * kNumGuarded,
65         PROT_READ | PROT_WRITE,
66         MAP_PRIVATE | MAP_ANONYMOUS,
67         -1,
68         0);
69     PCHECK(p != (void*)(-1));
70     storage_ = reinterpret_cast<unsigned char*>(p);
71
72     /* Protect the bottommost page of every stack allocation */
73     for (size_t i = 0; i < kNumGuarded; ++i) {
74       auto allocBegin = storage_ + allocSize_ * i;
75       freeList_.emplace_back(allocBegin, /* protected= */ false);
76     }
77   }
78
79   unsigned char* borrow(size_t size) {
80     std::lock_guard<folly::SpinLock> lg(lock_);
81
82     assert(storage_);
83
84     auto as = allocSize(size);
85     if (as != allocSize_ || freeList_.empty()) {
86       return nullptr;
87     }
88
89     auto p = freeList_.back().first;
90     if (!freeList_.back().second) {
91       PCHECK(0 == ::mprotect(p, pagesize(), PROT_NONE));
92       SYNCHRONIZED(pages, protectedPages()) {
93         pages.insert(reinterpret_cast<intptr_t>(p));
94       }
95     }
96     freeList_.pop_back();
97
98     /* We allocate minimum number of pages required, plus a guard page.
99        Since we use this for stack storage, requested allocation is aligned
100        at the top of the allocated pages, while the guard page is at the bottom.
101
102                -- increasing addresses -->
103              Guard page     Normal pages
104             |xxxxxxxxxx|..........|..........|
105             <- allocSize_ ------------------->
106          p -^                <- size -------->
107                       limit -^
108     */
109     auto limit = p + allocSize_ - size;
110     assert(limit >= p + pagesize());
111     return limit;
112   }
113
114   bool giveBack(unsigned char* limit, size_t size) {
115     std::lock_guard<folly::SpinLock> lg(lock_);
116
117     assert(storage_);
118
119     auto as = allocSize(size);
120     auto p = limit + size - as;
121     if (p < storage_ || p >= storage_ + allocSize_ * kNumGuarded) {
122       /* not mine */
123       return false;
124     }
125
126     assert(as == allocSize_);
127     assert((p - storage_) % allocSize_ == 0);
128     freeList_.emplace_back(p, /* protected= */ true);
129     return true;
130   }
131
132   ~StackCache() {
133     assert(storage_);
134     SYNCHRONIZED(pages, protectedPages()) {
135       for (const auto& item : freeList_) {
136         pages.erase(reinterpret_cast<intptr_t>(item.first));
137       }
138     }
139     PCHECK(0 == ::munmap(storage_, allocSize_ * kNumGuarded));
140   }
141
142   static bool isProtected(intptr_t addr) {
143     // Use a read lock for reading.
144     SYNCHRONIZED_CONST(pages, protectedPages()) {
145       for (const auto& page : pages) {
146         intptr_t pageEnd = page + pagesize();
147         if (page <= addr && addr < pageEnd) {
148           return true;
149         }
150       }
151     }
152     return false;
153   }
154
155  private:
156   folly::SpinLock lock_;
157   unsigned char* storage_{nullptr};
158   size_t allocSize_{0};
159
160   /**
161    * LIFO free list. Each pair contains stack pointer and protected flag.
162    */
163   std::vector<std::pair<unsigned char*, bool>> freeList_;
164
165   static size_t pagesize() {
166     static const size_t pagesize = sysconf(_SC_PAGESIZE);
167     return pagesize;
168   }
169
170   /* Returns a multiple of pagesize() enough to store size + one guard page */
171   static size_t allocSize(size_t size) {
172     return pagesize() * ((size + pagesize() - 1) / pagesize() + 1);
173   }
174
175   static folly::Synchronized<std::unordered_set<intptr_t>>& protectedPages() {
176     static auto instance =
177         new folly::Synchronized<std::unordered_set<intptr_t>>();
178     return *instance;
179   }
180 };
181
182 #ifndef _WIN32
183
184 namespace {
185
186 struct sigaction oldSigsegvAction;
187
188 void sigsegvSignalHandler(int signum, siginfo_t* info, void*) {
189   if (signum != SIGSEGV) {
190     std::cerr << "GuardPageAllocator signal handler called for signal: "
191               << signum;
192     return;
193   }
194
195   if (info &&
196       StackCache::isProtected(reinterpret_cast<intptr_t>(info->si_addr))) {
197     std::cerr << "folly::fibers Fiber stack overflow detected." << std::endl;
198   }
199
200   // Restore old signal handler and let it handle the signal.
201   sigaction(signum, &oldSigsegvAction, nullptr);
202   raise(signum);
203 }
204
205 bool isInJVM() {
206   auto getCreated = dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs");
207   return getCreated;
208 }
209
210 void installSignalHandler() {
211   static std::once_flag onceFlag;
212   std::call_once(onceFlag, []() {
213     if (isInJVM()) {
214       // Don't install signal handler, since JVM internal signal handler doesn't
215       // work with SA_ONSTACK
216       return;
217     }
218
219     struct sigaction sa;
220     memset(&sa, 0, sizeof(sa));
221     sigemptyset(&sa.sa_mask);
222     // By default signal handlers are run on the signaled thread's stack.
223     // In case of stack overflow running the SIGSEGV signal handler on
224     // the same stack leads to another SIGSEGV and crashes the program.
225     // Use SA_ONSTACK, so alternate stack is used (only if configured via
226     // sigaltstack).
227     sa.sa_flags |= SA_SIGINFO | SA_ONSTACK;
228     sa.sa_sigaction = &sigsegvSignalHandler;
229     sigaction(SIGSEGV, &sa, &oldSigsegvAction);
230   });
231 }
232 }
233
234 #endif
235
236 class CacheManager {
237  public:
238   static CacheManager& instance() {
239     static auto inst = new CacheManager();
240     return *inst;
241   }
242
243   std::unique_ptr<StackCacheEntry> getStackCache(size_t stackSize) {
244     std::lock_guard<folly::SpinLock> lg(lock_);
245     if (inUse_ < kMaxInUse) {
246       ++inUse_;
247       return folly::make_unique<StackCacheEntry>(stackSize);
248     }
249
250     return nullptr;
251   }
252
253  private:
254   folly::SpinLock lock_;
255   size_t inUse_{0};
256
257   friend class StackCacheEntry;
258
259   void giveBack(std::unique_ptr<StackCache> /* stackCache_ */) {
260     assert(inUse_ > 0);
261     --inUse_;
262     /* Note: we can add a free list for each size bucket
263        if stack re-use is important.
264        In this case this needs to be a folly::Singleton
265        to make sure the free list is cleaned up on fork.
266
267        TODO(t7351705): fix Singleton destruction order
268     */
269   }
270 };
271
272 /*
273  * RAII Wrapper around a StackCache that calls
274  * CacheManager::giveBack() on destruction.
275  */
276 class StackCacheEntry {
277  public:
278   explicit StackCacheEntry(size_t stackSize)
279       : stackCache_(folly::make_unique<StackCache>(stackSize)) {}
280
281   StackCache& cache() const noexcept {
282     return *stackCache_;
283   }
284
285   ~StackCacheEntry() {
286     CacheManager::instance().giveBack(std::move(stackCache_));
287   }
288
289  private:
290   std::unique_ptr<StackCache> stackCache_;
291 };
292
293 GuardPageAllocator::GuardPageAllocator(bool useGuardPages)
294     : useGuardPages_(useGuardPages) {
295 #ifndef _WIN32
296   installSignalHandler();
297 #endif
298 }
299
300 GuardPageAllocator::~GuardPageAllocator() = default;
301
302 unsigned char* GuardPageAllocator::allocate(size_t size) {
303   if (useGuardPages_ && !stackCache_) {
304     stackCache_ = CacheManager::instance().getStackCache(size);
305   }
306
307   if (stackCache_) {
308     auto p = stackCache_->cache().borrow(size);
309     if (p != nullptr) {
310       return p;
311     }
312   }
313   return fallbackAllocator_.allocate(size);
314 }
315
316 void GuardPageAllocator::deallocate(unsigned char* limit, size_t size) {
317   if (!(stackCache_ && stackCache_->cache().giveBack(limit, size))) {
318     fallbackAllocator_.deallocate(limit, size);
319   }
320 }
321 }
322 } // folly::fibers