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