xref: /llvm-project/llvm/tools/dsymutil/BinaryHolder.cpp (revision 5886454669c3c9026f7f27eab13509dd0241f2d6)
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(0, Filename.rfind('('));
25   StringRef Object = Filename.substr(Archive.size() + 1).drop_back();
26   return {Archive, Object};
27 }
28 
29 static bool isArchive(StringRef Filename) { return Filename.ends_with(")"); }
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(It->getOffset(), It->getSize());
39     Buffers.emplace_back(ObjData, Filename);
40   }
41   return Buffers;
42 }
43 
44 BinaryHolder::BinaryHolder(IntrusiveRefCntPtr<vfs::FileSystem> VFS,
45                            BinaryHolder::Options Opts)
46     : VFS(VFS), Opts(Opts) {}
47 
48 Error 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(ArchiveFilename, -1, false);
57   if (auto Err = ErrOrBuff.getError())
58     return errorCodeToError(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(MemBuffer->getMemBufferRef());
71   if (!ErrOrFat) {
72     consumeError(ErrOrFat.takeError());
73     ArchiveBuffers.push_back(MemBuffer->getMemBufferRef());
74   } else {
75     FatBinary = std::move(*ErrOrFat);
76     FatBinaryName = std::string(ArchiveFilename);
77     ArchiveBuffers =
78         getMachOFatMemoryBuffers(FatBinaryName, *MemBuffer, *FatBinary);
79   }
80 
81   // Finally, try to load the archives.
82   Archives.reserve(ArchiveBuffers.size());
83   for (auto MemRef : ArchiveBuffers) {
84     auto ErrOrArchive = object::Archive::create(MemRef);
85     if (!ErrOrArchive)
86       return ErrOrArchive.takeError();
87     Archives.push_back(std::move(*ErrOrArchive));
88   }
89 
90   return Error::success();
91 }
92 
93 Error 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(Filename, -1, false);
100   if (auto Err = ErrOrBuff.getError())
101     return errorCodeToError(Err);
102 
103   if (Opts.Warn && Filename != "-" && Timestamp != sys::TimePoint<>()) {
104     llvm::ErrorOr<vfs::Status> Stat = VFS->status(Filename);
105     if (!Stat)
106       return errorCodeToError(Stat.getError());
107     if (Timestamp != std::chrono::time_point_cast<std::chrono::seconds>(
108                          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(MemBuffer->getMemBufferRef());
126   if (!ErrOrFat) {
127     consumeError(ErrOrFat.takeError());
128     ObjectBuffers.push_back(MemBuffer->getMemBufferRef());
129   } else {
130     FatBinary = std::move(*ErrOrFat);
131     FatBinaryName = std::string(Filename);
132     ObjectBuffers =
133         getMachOFatMemoryBuffers(FatBinaryName, *MemBuffer, *FatBinary);
134   }
135 
136   Objects.reserve(ObjectBuffers.size());
137   for (auto MemRef : ObjectBuffers) {
138     auto ErrOrObjectFile = object::ObjectFile::createObjectFile(MemRef);
139     if (!ErrOrObjectFile)
140       return ErrOrObjectFile.takeError();
141     Objects.push_back(std::move(*ErrOrObjectFile));
142   }
143 
144   return Error::success();
145 }
146 
147 std::vector<const object::ObjectFile *>
148 BinaryHolder::ObjectEntry::getObjects() const {
149   std::vector<const object::ObjectFile *> Result;
150   Result.reserve(Objects.size());
151   for (auto &Object : Objects) {
152     Result.push_back(Object.get());
153   }
154   return Result;
155 }
156 Expected<const object::ObjectFile &>
157 BinaryHolder::ObjectEntry::getObject(const Triple &T) const {
158   for (const auto &Obj : Objects) {
159     if (const auto *MachO = dyn_cast<object::MachOObjectFile>(Obj.get())) {
160       if (MachO->getArchTriple().str() == T.str())
161         return *MachO;
162     } else if (Obj->getArch() == T.getArch())
163       return *Obj;
164   }
165   return errorCodeToError(object::object_error::arch_not_found);
166 }
167 
168 Expected<const BinaryHolder::ObjectEntry &>
169 BinaryHolder::ArchiveEntry::getObjectEntry(StringRef Filename,
170                                            TimestampTy Timestamp,
171                                            Options Opts) {
172   StringRef ArchiveFilename;
173   StringRef ObjectFilename;
174   std::tie(ArchiveFilename, ObjectFilename) = getArchiveAndObjectName(Filename);
175   KeyTy Key = {ObjectFilename, Timestamp};
176 
177   // Try the cache first.
178   std::lock_guard<std::mutex> Lock(MemberCacheMutex);
179   if (MemberCache.count(Key))
180     return *MemberCache[Key];
181 
182   // Create a new ObjectEntry, but don't add it to the cache yet. Loading of
183   // the archive members might fail and we don't want to lock the whole archive
184   // during this operation.
185   auto OE = std::make_unique<ObjectEntry>();
186 
187   for (const auto &Archive : Archives) {
188     Error Err = Error::success();
189     for (const auto &Child : Archive->children(Err)) {
190       if (auto NameOrErr = Child.getName()) {
191         if (*NameOrErr == ObjectFilename) {
192           auto ModTimeOrErr = Child.getLastModified();
193           if (!ModTimeOrErr)
194             return ModTimeOrErr.takeError();
195 
196           if (Timestamp != sys::TimePoint<>() &&
197               Timestamp != std::chrono::time_point_cast<std::chrono::seconds>(
198                                ModTimeOrErr.get())) {
199             if (Opts.Verbose)
200               WithColor::warning()
201                   << *NameOrErr
202                   << ": timestamp mismatch between archive member ("
203                   << ModTimeOrErr.get() << ") and debug map (" << Timestamp
204                   << ")\n";
205             continue;
206           }
207 
208           if (Opts.Verbose)
209             WithColor::note() << "found member in archive.\n";
210 
211           auto ErrOrMem = Child.getMemoryBufferRef();
212           if (!ErrOrMem)
213             return ErrOrMem.takeError();
214 
215           auto ErrOrObjectFile =
216               object::ObjectFile::createObjectFile(*ErrOrMem);
217           if (!ErrOrObjectFile)
218             return ErrOrObjectFile.takeError();
219 
220           OE->Objects.push_back(std::move(*ErrOrObjectFile));
221         }
222       }
223     }
224     if (Err)
225       return std::move(Err);
226   }
227 
228   if (OE->Objects.empty())
229     return errorCodeToError(errc::no_such_file_or_directory);
230 
231   MemberCache[Key] = std::move(OE);
232   return *MemberCache[Key];
233 }
234 
235 Expected<const BinaryHolder::ObjectEntry &>
236 BinaryHolder::getObjectEntry(StringRef Filename, TimestampTy Timestamp) {
237   if (Opts.Verbose)
238     WithColor::note() << "trying to open '" << Filename << "'\n";
239 
240   // If this is an archive, we might have either the object or the archive
241   // cached. In this case we can load it without accessing the file system.
242   if (isArchive(Filename)) {
243     StringRef ArchiveFilename = getArchiveAndObjectName(Filename).first;
244     std::lock_guard<std::mutex> Lock(ArchiveCacheMutex);
245     ArchiveRefCounter[ArchiveFilename]++;
246     if (ArchiveCache.count(ArchiveFilename)) {
247       return ArchiveCache[ArchiveFilename]->getObjectEntry(Filename, Timestamp,
248                                                            Opts);
249     } else {
250       auto AE = std::make_unique<ArchiveEntry>();
251       auto Err = AE->load(VFS, Filename, Timestamp, Opts);
252       if (Err) {
253         // Don't return the error here: maybe the file wasn't an archive.
254         llvm::consumeError(std::move(Err));
255       } else {
256         ArchiveCache[ArchiveFilename] = std::move(AE);
257         return ArchiveCache[ArchiveFilename]->getObjectEntry(Filename,
258                                                              Timestamp, Opts);
259       }
260     }
261   }
262 
263   // If this is an object, we might have it cached. If not we'll have to load
264   // it from the file system and cache it now.
265   std::lock_guard<std::mutex> Lock(ObjectCacheMutex);
266   ObjectRefCounter[Filename]++;
267   if (!ObjectCache.count(Filename)) {
268     auto OE = std::make_unique<ObjectEntry>();
269     auto Err = OE->load(VFS, Filename, Timestamp, Opts);
270     if (Err)
271       return std::move(Err);
272     ObjectCache[Filename] = std::move(OE);
273   }
274 
275   return *ObjectCache[Filename];
276 }
277 
278 void BinaryHolder::clear() {
279   std::lock_guard<std::mutex> ArchiveLock(ArchiveCacheMutex);
280   std::lock_guard<std::mutex> ObjectLock(ObjectCacheMutex);
281   ArchiveCache.clear();
282   ObjectCache.clear();
283 }
284 
285 void BinaryHolder::eraseObjectEntry(StringRef Filename) {
286   if (Opts.Verbose)
287     WithColor::note() << "erasing '" << Filename << "' from cache\n";
288 
289   if (isArchive(Filename)) {
290     StringRef ArchiveFilename = getArchiveAndObjectName(Filename).first;
291     std::lock_guard<std::mutex> Lock(ArchiveCacheMutex);
292     ArchiveRefCounter[ArchiveFilename]--;
293     if (ArchiveRefCounter[ArchiveFilename] == 0)
294       ArchiveCache.erase(ArchiveFilename);
295     return;
296   }
297 
298   std::lock_guard<std::mutex> Lock(ObjectCacheMutex);
299   ObjectRefCounter[Filename]--;
300   if (ObjectRefCounter[Filename] == 0)
301     ObjectCache.erase(Filename);
302 }
303 
304 } // namespace dsymutil
305 } // namespace llvm
306