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