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
44Error 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
89Error 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
143std::vector<const object::ObjectFile *>
144BinaryHolder::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}
152Expected<const object::ObjectFile &>
153BinaryHolder::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
164Expected<const BinaryHolder::ObjectEntry &>
165BinaryHolder::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
231Expected<const BinaryHolder::ObjectEntry &>
232BinaryHolder::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
274void 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
281void 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