5bdd075668eecae53ed4ead101bcc158142d210d
[folly.git] / folly / experimental / symbolizer / ElfCache.cpp
1 /*
2  * Copyright 2017 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
17 #include <folly/experimental/symbolizer/ElfCache.h>
18
19 #include <link.h>
20
21 /*
22  * This is declared in `link.h' on Linux platforms, but apparently not on the
23  * Mac version of the file.  It's harmless to declare again, in any case.
24  *
25  * Note that declaring it with `extern "C"` results in linkage conflicts.
26  */
27 extern struct r_debug _r_debug;
28
29 namespace folly { namespace symbolizer {
30
31 size_t countLoadedElfFiles() {
32   // _r_debug synchronization is... lacking to say the least. It's
33   // meant as an aid for debuggers and synchronization is done by
34   // calling dl_debug_state() which debuggers are supposed to
35   // intercept by setting a breakpoint on.
36
37   // Can't really do that here, so we apply the hope-and-pray strategy.
38   if (_r_debug.r_version != 1 || _r_debug.r_state != r_debug::RT_CONSISTENT) {
39     // computo ergo sum
40     return 1;
41   }
42
43   //     r_map       -> head of a linked list of 'link_map_t' entries,
44   //                    one per ELF 'binary' in the process address space.
45   size_t count = 0;
46   for (auto lmap = _r_debug.r_map; lmap != nullptr; lmap = lmap->l_next) {
47     ++count;
48   }
49   return count;
50 }
51
52 SignalSafeElfCache::SignalSafeElfCache(size_t capacity) {
53   map_.reserve(capacity);
54   slots_.reserve(capacity);
55
56   // Preallocate
57   for (size_t i = 0; i < capacity; ++i) {
58     slots_.push_back(std::make_shared<ElfFile>());
59   }
60 }
61
62 std::shared_ptr<ElfFile> SignalSafeElfCache::getFile(StringPiece p) {
63   if (p.size() > Path::kMaxSize) {
64     return nullptr;
65   }
66
67   scratchpad_.assign(p);
68   auto pos = map_.find(scratchpad_);
69   if (pos != map_.end()) {
70     return slots_[pos->second];
71   }
72
73   size_t n = map_.size();
74   if (n >= slots_.size()) {
75     DCHECK_EQ(map_.size(), slots_.size());
76     return nullptr;
77   }
78
79   auto& f = slots_[n];
80
81   const char* msg = "";
82   int r = f->openAndFollow(scratchpad_.data(), true, &msg);
83   if (r != ElfFile::kSuccess) {
84     return nullptr;
85   }
86
87   map_[scratchpad_] = n;
88   return f;
89 }
90
91 ElfCache::ElfCache(size_t capacity) : capacity_(capacity) { }
92
93 std::shared_ptr<ElfFile> ElfCache::getFile(StringPiece p) {
94   std::lock_guard<std::mutex> lock(mutex_);
95
96   auto pos = files_.find(p);
97   if (pos != files_.end()) {
98     // Found, move to back (MRU)
99     auto& entry = pos->second;
100     lruList_.erase(lruList_.iterator_to(*entry));
101     lruList_.push_back(*entry);
102     return filePtr(entry);
103   }
104
105   auto entry = std::make_shared<Entry>();
106   entry->path = p.str();
107   auto& path = entry->path;
108
109   // No negative caching
110   const char* msg = "";
111   int r = entry->file.openAndFollow(path.c_str(), true, &msg);
112   if (r != ElfFile::kSuccess) {
113     return nullptr;
114   }
115
116   if (files_.size() == capacity_) {
117     auto& e = lruList_.front();
118     lruList_.pop_front();
119     files_.erase(e.path);
120   }
121
122   files_.emplace(entry->path, entry);
123   lruList_.push_back(*entry);
124
125   return filePtr(entry);
126 }
127
128 std::shared_ptr<ElfFile> ElfCache::filePtr(const std::shared_ptr<Entry>& e) {
129   // share ownership
130   return std::shared_ptr<ElfFile>(e, &e->file);
131 }
132
133 }}  // namespaces