1 | //===-- BinaryHolder.cpp --------------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This program is a utility that aims to be a dropin replacement for |
10 | // Darwin's dsymutil. |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #include "BinaryHolder.h" |
15 | #include "llvm/Object/MachO.h" |
16 | #include "llvm/Support/WithColor.h" |
17 | #include "llvm/Support/raw_ostream.h" |
18 | |
19 | namespace llvm { |
20 | namespace dsymutil { |
21 | |
22 | static std::pair<StringRef, StringRef> |
23 | getArchiveAndObjectName(StringRef Filename) { |
24 | StringRef Archive = Filename.substr(Start: 0, N: Filename.rfind(C: '(')); |
25 | StringRef Object = Filename.substr(Start: Archive.size() + 1).drop_back(); |
26 | return {Archive, Object}; |
27 | } |
28 | |
29 | static bool isArchive(StringRef Filename) { return Filename.ends_with(Suffix: ")" ); } |
30 | |
31 | static std::vector<MemoryBufferRef> |
32 | getMachOFatMemoryBuffers(StringRef Filename, MemoryBuffer &Mem, |
33 | object::MachOUniversalBinary &Fat) { |
34 | std::vector<MemoryBufferRef> Buffers; |
35 | StringRef FatData = Fat.getData(); |
36 | for (auto It = Fat.begin_objects(), End = Fat.end_objects(); It != End; |
37 | ++It) { |
38 | StringRef ObjData = FatData.substr(Start: It->getOffset(), N: It->getSize()); |
39 | Buffers.emplace_back(args&: ObjData, args&: Filename); |
40 | } |
41 | return Buffers; |
42 | } |
43 | |
44 | Error BinaryHolder::ArchiveEntry::load(IntrusiveRefCntPtr<vfs::FileSystem> VFS, |
45 | StringRef Filename, |
46 | TimestampTy Timestamp, bool Verbose) { |
47 | StringRef ArchiveFilename = getArchiveAndObjectName(Filename).first; |
48 | |
49 | // Try to load archive and force it to be memory mapped. |
50 | auto ErrOrBuff = (ArchiveFilename == "-" ) |
51 | ? MemoryBuffer::getSTDIN() |
52 | : VFS->getBufferForFile(Name: ArchiveFilename, FileSize: -1, RequiresNullTerminator: false); |
53 | if (auto Err = ErrOrBuff.getError()) |
54 | return errorCodeToError(EC: Err); |
55 | |
56 | MemBuffer = std::move(*ErrOrBuff); |
57 | |
58 | if (Verbose) |
59 | WithColor::note() << "loaded archive '" << ArchiveFilename << "'\n" ; |
60 | |
61 | // Load one or more archive buffers, depending on whether we're dealing with |
62 | // a fat binary. |
63 | std::vector<MemoryBufferRef> ArchiveBuffers; |
64 | |
65 | auto ErrOrFat = |
66 | object::MachOUniversalBinary::create(Source: MemBuffer->getMemBufferRef()); |
67 | if (!ErrOrFat) { |
68 | consumeError(Err: ErrOrFat.takeError()); |
69 | ArchiveBuffers.push_back(x: MemBuffer->getMemBufferRef()); |
70 | } else { |
71 | FatBinary = std::move(*ErrOrFat); |
72 | FatBinaryName = std::string(ArchiveFilename); |
73 | ArchiveBuffers = |
74 | getMachOFatMemoryBuffers(Filename: FatBinaryName, Mem&: *MemBuffer, Fat&: *FatBinary); |
75 | } |
76 | |
77 | // Finally, try to load the archives. |
78 | Archives.reserve(n: ArchiveBuffers.size()); |
79 | for (auto MemRef : ArchiveBuffers) { |
80 | auto ErrOrArchive = object::Archive::create(Source: MemRef); |
81 | if (!ErrOrArchive) |
82 | return ErrOrArchive.takeError(); |
83 | Archives.push_back(x: std::move(*ErrOrArchive)); |
84 | } |
85 | |
86 | return Error::success(); |
87 | } |
88 | |
89 | Error BinaryHolder::ObjectEntry::load(IntrusiveRefCntPtr<vfs::FileSystem> VFS, |
90 | StringRef Filename, TimestampTy Timestamp, |
91 | bool Verbose) { |
92 | // Try to load regular binary and force it to be memory mapped. |
93 | auto ErrOrBuff = (Filename == "-" ) |
94 | ? MemoryBuffer::getSTDIN() |
95 | : VFS->getBufferForFile(Name: Filename, FileSize: -1, RequiresNullTerminator: false); |
96 | if (auto Err = ErrOrBuff.getError()) |
97 | return errorCodeToError(EC: Err); |
98 | |
99 | if (Filename != "-" && Timestamp != sys::TimePoint<>()) { |
100 | llvm::ErrorOr<vfs::Status> Stat = VFS->status(Path: Filename); |
101 | if (!Stat) |
102 | return errorCodeToError(EC: Stat.getError()); |
103 | if (Timestamp != std::chrono::time_point_cast<std::chrono::seconds>( |
104 | t: Stat->getLastModificationTime())) |
105 | WithColor::warning() << Filename |
106 | << ": timestamp mismatch between object file (" |
107 | << Stat->getLastModificationTime() |
108 | << ") and debug map (" << Timestamp << ")\n" ; |
109 | } |
110 | |
111 | MemBuffer = std::move(*ErrOrBuff); |
112 | |
113 | if (Verbose) |
114 | WithColor::note() << "loaded object.\n" ; |
115 | |
116 | // Load one or more object buffers, depending on whether we're dealing with a |
117 | // fat binary. |
118 | std::vector<MemoryBufferRef> ObjectBuffers; |
119 | |
120 | auto ErrOrFat = |
121 | object::MachOUniversalBinary::create(Source: MemBuffer->getMemBufferRef()); |
122 | if (!ErrOrFat) { |
123 | consumeError(Err: ErrOrFat.takeError()); |
124 | ObjectBuffers.push_back(x: MemBuffer->getMemBufferRef()); |
125 | } else { |
126 | FatBinary = std::move(*ErrOrFat); |
127 | FatBinaryName = std::string(Filename); |
128 | ObjectBuffers = |
129 | getMachOFatMemoryBuffers(Filename: FatBinaryName, Mem&: *MemBuffer, Fat&: *FatBinary); |
130 | } |
131 | |
132 | Objects.reserve(n: ObjectBuffers.size()); |
133 | for (auto MemRef : ObjectBuffers) { |
134 | auto ErrOrObjectFile = object::ObjectFile::createObjectFile(Object: MemRef); |
135 | if (!ErrOrObjectFile) |
136 | return ErrOrObjectFile.takeError(); |
137 | Objects.push_back(x: std::move(*ErrOrObjectFile)); |
138 | } |
139 | |
140 | return Error::success(); |
141 | } |
142 | |
143 | std::vector<const object::ObjectFile *> |
144 | BinaryHolder::ObjectEntry::getObjects() const { |
145 | std::vector<const object::ObjectFile *> Result; |
146 | Result.reserve(n: Objects.size()); |
147 | for (auto &Object : Objects) { |
148 | Result.push_back(x: Object.get()); |
149 | } |
150 | return Result; |
151 | } |
152 | Expected<const object::ObjectFile &> |
153 | BinaryHolder::ObjectEntry::getObject(const Triple &T) const { |
154 | for (const auto &Obj : Objects) { |
155 | if (const auto *MachO = dyn_cast<object::MachOObjectFile>(Val: Obj.get())) { |
156 | if (MachO->getArchTriple().str() == T.str()) |
157 | return *MachO; |
158 | } else if (Obj->getArch() == T.getArch()) |
159 | return *Obj; |
160 | } |
161 | return errorCodeToError(EC: object::object_error::arch_not_found); |
162 | } |
163 | |
164 | Expected<const BinaryHolder::ObjectEntry &> |
165 | BinaryHolder::ArchiveEntry::getObjectEntry(StringRef Filename, |
166 | TimestampTy Timestamp, |
167 | bool Verbose) { |
168 | StringRef ArchiveFilename; |
169 | StringRef ObjectFilename; |
170 | std::tie(args&: ArchiveFilename, args&: ObjectFilename) = getArchiveAndObjectName(Filename); |
171 | KeyTy Key = {ObjectFilename, Timestamp}; |
172 | |
173 | // Try the cache first. |
174 | std::lock_guard<std::mutex> Lock(MemberCacheMutex); |
175 | if (MemberCache.count(Val: Key)) |
176 | return *MemberCache[Key]; |
177 | |
178 | // Create a new ObjectEntry, but don't add it to the cache yet. Loading of |
179 | // the archive members might fail and we don't want to lock the whole archive |
180 | // during this operation. |
181 | auto OE = std::make_unique<ObjectEntry>(); |
182 | |
183 | for (const auto &Archive : Archives) { |
184 | Error Err = Error::success(); |
185 | for (const auto &Child : Archive->children(Err)) { |
186 | if (auto NameOrErr = Child.getName()) { |
187 | if (*NameOrErr == ObjectFilename) { |
188 | auto ModTimeOrErr = Child.getLastModified(); |
189 | if (!ModTimeOrErr) |
190 | return ModTimeOrErr.takeError(); |
191 | |
192 | if (Timestamp != sys::TimePoint<>() && |
193 | Timestamp != std::chrono::time_point_cast<std::chrono::seconds>( |
194 | t: ModTimeOrErr.get())) { |
195 | if (Verbose) |
196 | WithColor::warning() |
197 | << *NameOrErr |
198 | << ": timestamp mismatch between archive member (" |
199 | << ModTimeOrErr.get() << ") and debug map (" << Timestamp |
200 | << ")\n" ; |
201 | continue; |
202 | } |
203 | |
204 | if (Verbose) |
205 | WithColor::note() << "found member in archive.\n" ; |
206 | |
207 | auto ErrOrMem = Child.getMemoryBufferRef(); |
208 | if (!ErrOrMem) |
209 | return ErrOrMem.takeError(); |
210 | |
211 | auto ErrOrObjectFile = |
212 | object::ObjectFile::createObjectFile(Object: *ErrOrMem); |
213 | if (!ErrOrObjectFile) |
214 | return ErrOrObjectFile.takeError(); |
215 | |
216 | OE->Objects.push_back(x: std::move(*ErrOrObjectFile)); |
217 | } |
218 | } |
219 | } |
220 | if (Err) |
221 | return std::move(Err); |
222 | } |
223 | |
224 | if (OE->Objects.empty()) |
225 | return errorCodeToError(EC: errc::no_such_file_or_directory); |
226 | |
227 | MemberCache[Key] = std::move(OE); |
228 | return *MemberCache[Key]; |
229 | } |
230 | |
231 | Expected<const BinaryHolder::ObjectEntry &> |
232 | BinaryHolder::getObjectEntry(StringRef Filename, TimestampTy Timestamp) { |
233 | if (Verbose) |
234 | WithColor::note() << "trying to open '" << Filename << "'\n" ; |
235 | |
236 | // If this is an archive, we might have either the object or the archive |
237 | // cached. In this case we can load it without accessing the file system. |
238 | if (isArchive(Filename)) { |
239 | StringRef ArchiveFilename = getArchiveAndObjectName(Filename).first; |
240 | std::lock_guard<std::mutex> Lock(ArchiveCacheMutex); |
241 | ArchiveRefCounter[ArchiveFilename]++; |
242 | if (ArchiveCache.count(Key: ArchiveFilename)) { |
243 | return ArchiveCache[ArchiveFilename]->getObjectEntry(Filename, Timestamp, |
244 | Verbose); |
245 | } else { |
246 | auto AE = std::make_unique<ArchiveEntry>(); |
247 | auto Err = AE->load(VFS, Filename, Timestamp, Verbose); |
248 | if (Err) { |
249 | // Don't return the error here: maybe the file wasn't an archive. |
250 | llvm::consumeError(Err: std::move(Err)); |
251 | } else { |
252 | ArchiveCache[ArchiveFilename] = std::move(AE); |
253 | return ArchiveCache[ArchiveFilename]->getObjectEntry( |
254 | Filename, Timestamp, Verbose); |
255 | } |
256 | } |
257 | } |
258 | |
259 | // If this is an object, we might have it cached. If not we'll have to load |
260 | // it from the file system and cache it now. |
261 | std::lock_guard<std::mutex> Lock(ObjectCacheMutex); |
262 | ObjectRefCounter[Filename]++; |
263 | if (!ObjectCache.count(Key: Filename)) { |
264 | auto OE = std::make_unique<ObjectEntry>(); |
265 | auto Err = OE->load(VFS, Filename, Timestamp, Verbose); |
266 | if (Err) |
267 | return std::move(Err); |
268 | ObjectCache[Filename] = std::move(OE); |
269 | } |
270 | |
271 | return *ObjectCache[Filename]; |
272 | } |
273 | |
274 | void BinaryHolder::clear() { |
275 | std::lock_guard<std::mutex> ArchiveLock(ArchiveCacheMutex); |
276 | std::lock_guard<std::mutex> ObjectLock(ObjectCacheMutex); |
277 | ArchiveCache.clear(); |
278 | ObjectCache.clear(); |
279 | } |
280 | |
281 | void BinaryHolder::eraseObjectEntry(StringRef Filename) { |
282 | if (Verbose) |
283 | WithColor::note() << "erasing '" << Filename << "' from cache\n" ; |
284 | |
285 | if (isArchive(Filename)) { |
286 | StringRef ArchiveFilename = getArchiveAndObjectName(Filename).first; |
287 | std::lock_guard<std::mutex> Lock(ArchiveCacheMutex); |
288 | ArchiveRefCounter[ArchiveFilename]--; |
289 | if (ArchiveRefCounter[ArchiveFilename] == 0) |
290 | ArchiveCache.erase(Key: ArchiveFilename); |
291 | return; |
292 | } |
293 | |
294 | std::lock_guard<std::mutex> Lock(ObjectCacheMutex); |
295 | ObjectRefCounter[Filename]--; |
296 | if (ObjectRefCounter[Filename] == 0) |
297 | ObjectCache.erase(Key: Filename); |
298 | } |
299 | |
300 | } // namespace dsymutil |
301 | } // namespace llvm |
302 | |