1/*
2 * Copyright 2014-present 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#pragma once
18
19#include <climits> // for PATH_MAX
20#include <cstring>
21#include <memory>
22#include <mutex>
23#include <string>
24#include <unordered_map>
25#include <vector>
26
27#include <boost/container/flat_map.hpp>
28#include <boost/intrusive/list.hpp>
29#include <boost/operators.hpp>
30#include <glog/logging.h>
31
32#include <folly/Range.h>
33#include <folly/experimental/symbolizer/Elf.h>
34#include <folly/hash/Hash.h>
35
36namespace folly {
37namespace symbolizer {
38
39/**
40 * Number of ELF files loaded by the dynamic loader.
41 */
42size_t countLoadedElfFiles();
43
44class ElfCacheBase {
45 public:
46 virtual std::shared_ptr<ElfFile> getFile(StringPiece path) = 0;
47 virtual ~ElfCacheBase() {}
48};
49
50/**
51 * Cache ELF files. Async-signal-safe: does memory allocation upfront.
52 *
53 * Will not grow; once the capacity is reached, lookups for files that
54 * aren't already in the cache will fail (return nullptr).
55 *
56 * Not MT-safe. May not be used concurrently from multiple threads.
57 *
58 * NOTE that async-signal-safety is preserved only as long as the
59 * SignalSafeElfCache object exists; after the SignalSafeElfCache object
60 * is destroyed, destroying returned shared_ptr<ElfFile> objects may
61 * cause ElfFile objects to be destroyed, and that's not async-signal-safe.
62 */
63class SignalSafeElfCache : public ElfCacheBase {
64 public:
65 explicit SignalSafeElfCache(size_t capacity);
66
67 std::shared_ptr<ElfFile> getFile(StringPiece path) override;
68
69 private:
70 // We can't use std::string (allocating memory is bad!) so we roll our
71 // own wrapper around a fixed-size, null-terminated string.
72 class Path : private boost::totally_ordered<Path> {
73 public:
74 Path() {
75 assign(folly::StringPiece());
76 }
77
78 explicit Path(StringPiece s) {
79 assign(s);
80 }
81
82 void assign(StringPiece s) {
83 DCHECK_LE(s.size(), kMaxSize);
84 if (!s.empty()) {
85 memcpy(data_, s.data(), s.size());
86 }
87 data_[s.size()] = '\0';
88 }
89
90 bool operator<(const Path& other) const {
91 return strcmp(data_, other.data_) < 0;
92 }
93
94 bool operator==(const Path& other) const {
95 return strcmp(data_, other.data_) == 0;
96 }
97
98 const char* data() const {
99 return data_;
100 }
101
102 static constexpr size_t kMaxSize = PATH_MAX - 1;
103
104 private:
105 char data_[kMaxSize + 1];
106 };
107
108 Path scratchpad_; // Preallocated key for map_ lookups.
109 boost::container::flat_map<Path, int> map_;
110 std::vector<std::shared_ptr<ElfFile>> slots_;
111};
112
113/**
114 * General-purpose ELF file cache.
115 *
116 * LRU of given capacity. MT-safe (uses locking). Not async-signal-safe.
117 */
118class ElfCache : public ElfCacheBase {
119 public:
120 explicit ElfCache(size_t capacity);
121
122 std::shared_ptr<ElfFile> getFile(StringPiece path) override;
123
124 private:
125 std::mutex mutex_;
126
127 typedef boost::intrusive::list_member_hook<> LruLink;
128
129 struct Entry {
130 std::string path;
131 ElfFile file;
132 LruLink lruLink;
133 };
134
135 static std::shared_ptr<ElfFile> filePtr(const std::shared_ptr<Entry>& e);
136
137 size_t capacity_;
138 std::unordered_map<StringPiece, std::shared_ptr<Entry>, Hash> files_;
139
140 typedef boost::intrusive::list<
141 Entry,
142 boost::intrusive::member_hook<Entry, LruLink, &Entry::lruLink>,
143 boost::intrusive::constant_time_size<false>>
144 LruList;
145 LruList lruList_;
146};
147} // namespace symbolizer
148} // namespace folly
149