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>
getArchiveAndObjectName(StringRef Filename)23 getArchiveAndObjectName(StringRef Filename) {
24 StringRef Archive = Filename.substr(0, Filename.rfind('('));
25 StringRef Object = Filename.substr(Archive.size() + 1).drop_back();
26 return {Archive, Object};
27 }
28
isArchive(StringRef Filename)29 static bool isArchive(StringRef Filename) { return Filename.endswith(")"); }
30
31 static std::vector<MemoryBufferRef>
getMachOFatMemoryBuffers(StringRef Filename,MemoryBuffer & Mem,object::MachOUniversalBinary & Fat)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(It->getOffset(), It->getSize());
39 Buffers.emplace_back(ObjData, Filename);
40 }
41 return Buffers;
42 }
43
load(IntrusiveRefCntPtr<vfs::FileSystem> VFS,StringRef Filename,TimestampTy Timestamp,bool Verbose)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(ArchiveFilename, -1, false);
53 if (auto Err = ErrOrBuff.getError())
54 return errorCodeToError(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(MemBuffer->getMemBufferRef());
67 if (!ErrOrFat) {
68 consumeError(ErrOrFat.takeError());
69 ArchiveBuffers.push_back(MemBuffer->getMemBufferRef());
70 } else {
71 FatBinary = std::move(*ErrOrFat);
72 FatBinaryName = std::string(ArchiveFilename);
73 ArchiveBuffers =
74 getMachOFatMemoryBuffers(FatBinaryName, *MemBuffer, *FatBinary);
75 }
76
77 // Finally, try to load the archives.
78 Archives.reserve(ArchiveBuffers.size());
79 for (auto MemRef : ArchiveBuffers) {
80 auto ErrOrArchive = object::Archive::create(MemRef);
81 if (!ErrOrArchive)
82 return ErrOrArchive.takeError();
83 Archives.push_back(std::move(*ErrOrArchive));
84 }
85
86 return Error::success();
87 }
88
load(IntrusiveRefCntPtr<vfs::FileSystem> VFS,StringRef Filename,bool Verbose)89 Error BinaryHolder::ObjectEntry::load(IntrusiveRefCntPtr<vfs::FileSystem> VFS,
90 StringRef Filename, bool Verbose) {
91 // Try to load regular binary and force it to be memory mapped.
92 auto ErrOrBuff = (Filename == "-")
93 ? MemoryBuffer::getSTDIN()
94 : VFS->getBufferForFile(Filename, -1, false);
95 if (auto Err = ErrOrBuff.getError())
96 return errorCodeToError(Err);
97
98 MemBuffer = std::move(*ErrOrBuff);
99
100 if (Verbose)
101 WithColor::note() << "loaded object.\n";
102
103 // Load one or more object buffers, depending on whether we're dealing with a
104 // fat binary.
105 std::vector<MemoryBufferRef> ObjectBuffers;
106
107 auto ErrOrFat =
108 object::MachOUniversalBinary::create(MemBuffer->getMemBufferRef());
109 if (!ErrOrFat) {
110 consumeError(ErrOrFat.takeError());
111 ObjectBuffers.push_back(MemBuffer->getMemBufferRef());
112 } else {
113 FatBinary = std::move(*ErrOrFat);
114 FatBinaryName = std::string(Filename);
115 ObjectBuffers =
116 getMachOFatMemoryBuffers(FatBinaryName, *MemBuffer, *FatBinary);
117 }
118
119 Objects.reserve(ObjectBuffers.size());
120 for (auto MemRef : ObjectBuffers) {
121 auto ErrOrObjectFile = object::ObjectFile::createObjectFile(MemRef);
122 if (!ErrOrObjectFile)
123 return ErrOrObjectFile.takeError();
124 Objects.push_back(std::move(*ErrOrObjectFile));
125 }
126
127 return Error::success();
128 }
129
130 std::vector<const object::ObjectFile *>
getObjects() const131 BinaryHolder::ObjectEntry::getObjects() const {
132 std::vector<const object::ObjectFile *> Result;
133 Result.reserve(Objects.size());
134 for (auto &Object : Objects) {
135 Result.push_back(Object.get());
136 }
137 return Result;
138 }
139 Expected<const object::ObjectFile &>
getObject(const Triple & T) const140 BinaryHolder::ObjectEntry::getObject(const Triple &T) const {
141 for (const auto &Obj : Objects) {
142 if (const auto *MachO = dyn_cast<object::MachOObjectFile>(Obj.get())) {
143 if (MachO->getArchTriple().str() == T.str())
144 return *MachO;
145 } else if (Obj->getArch() == T.getArch())
146 return *Obj;
147 }
148 return errorCodeToError(object::object_error::arch_not_found);
149 }
150
151 Expected<const BinaryHolder::ObjectEntry &>
getObjectEntry(StringRef Filename,TimestampTy Timestamp,bool Verbose)152 BinaryHolder::ArchiveEntry::getObjectEntry(StringRef Filename,
153 TimestampTy Timestamp,
154 bool Verbose) {
155 StringRef ArchiveFilename;
156 StringRef ObjectFilename;
157 std::tie(ArchiveFilename, ObjectFilename) = getArchiveAndObjectName(Filename);
158
159 // Try the cache first.
160 KeyTy Key = {ObjectFilename, Timestamp};
161
162 {
163 std::lock_guard<std::mutex> Lock(MemberCacheMutex);
164 if (MemberCache.count(Key))
165 return MemberCache[Key];
166 }
167
168 // Create a new ObjectEntry, but don't add it to the cache yet. Loading of
169 // the archive members might fail and we don't want to lock the whole archive
170 // during this operation.
171 ObjectEntry OE;
172
173 for (const auto &Archive : Archives) {
174 Error Err = Error::success();
175 for (auto Child : Archive->children(Err)) {
176 if (auto NameOrErr = Child.getName()) {
177 if (*NameOrErr == ObjectFilename) {
178 auto ModTimeOrErr = Child.getLastModified();
179 if (!ModTimeOrErr)
180 return ModTimeOrErr.takeError();
181
182 if (Timestamp != sys::TimePoint<>() &&
183 Timestamp != ModTimeOrErr.get()) {
184 if (Verbose)
185 WithColor::warning() << "member has timestamp mismatch.\n";
186 continue;
187 }
188
189 if (Verbose)
190 WithColor::note() << "found member in archive.\n";
191
192 auto ErrOrMem = Child.getMemoryBufferRef();
193 if (!ErrOrMem)
194 return ErrOrMem.takeError();
195
196 auto ErrOrObjectFile =
197 object::ObjectFile::createObjectFile(*ErrOrMem);
198 if (!ErrOrObjectFile)
199 return ErrOrObjectFile.takeError();
200
201 OE.Objects.push_back(std::move(*ErrOrObjectFile));
202 }
203 }
204 }
205 if (Err)
206 return std::move(Err);
207 }
208
209 if (OE.Objects.empty())
210 return errorCodeToError(errc::no_such_file_or_directory);
211
212 std::lock_guard<std::mutex> Lock(MemberCacheMutex);
213 MemberCache.try_emplace(Key, std::move(OE));
214 return MemberCache[Key];
215 }
216
217 Expected<const BinaryHolder::ObjectEntry &>
getObjectEntry(StringRef Filename,TimestampTy Timestamp)218 BinaryHolder::getObjectEntry(StringRef Filename, TimestampTy Timestamp) {
219 if (Verbose)
220 WithColor::note() << "trying to open '" << Filename << "'\n";
221
222 // If this is an archive, we might have either the object or the archive
223 // cached. In this case we can load it without accessing the file system.
224 if (isArchive(Filename)) {
225 StringRef ArchiveFilename = getArchiveAndObjectName(Filename).first;
226 std::lock_guard<std::mutex> Lock(ArchiveCacheMutex);
227 if (ArchiveCache.count(ArchiveFilename)) {
228 return ArchiveCache[ArchiveFilename].getObjectEntry(Filename, Timestamp,
229 Verbose);
230 } else {
231 ArchiveEntry &AE = ArchiveCache[ArchiveFilename];
232 auto Err = AE.load(VFS, Filename, Timestamp, Verbose);
233 if (Err) {
234 ArchiveCache.erase(ArchiveFilename);
235 // Don't return the error here: maybe the file wasn't an archive.
236 llvm::consumeError(std::move(Err));
237 } else {
238 return ArchiveCache[ArchiveFilename].getObjectEntry(Filename, Timestamp,
239 Verbose);
240 }
241 }
242 }
243
244 // If this is an object, we might have it cached. If not we'll have to load
245 // it from the file system and cache it now.
246 std::lock_guard<std::mutex> Lock(ObjectCacheMutex);
247 if (!ObjectCache.count(Filename)) {
248 ObjectEntry &OE = ObjectCache[Filename];
249 auto Err = OE.load(VFS, Filename, Verbose);
250 if (Err) {
251 ObjectCache.erase(Filename);
252 return std::move(Err);
253 }
254 }
255
256 return ObjectCache[Filename];
257 }
258
clear()259 void BinaryHolder::clear() {
260 std::lock_guard<std::mutex> ArchiveLock(ArchiveCacheMutex);
261 std::lock_guard<std::mutex> ObjectLock(ObjectCacheMutex);
262 ArchiveCache.clear();
263 ObjectCache.clear();
264 }
265
266 } // namespace dsymutil
267 } // namespace llvm
268