xref: /openbsd-src/gnu/llvm/llvm/tools/dsymutil/BinaryHolder.cpp (revision d415bd752c734aee168c4ee86ff32e8cc249eb16)
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,TimestampTy Timestamp,bool Verbose)89 Error 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(Filename, -1, false);
96   if (auto Err = ErrOrBuff.getError())
97     return errorCodeToError(Err);
98 
99   if (Filename != "-" && Timestamp != sys::TimePoint<>()) {
100     llvm::ErrorOr<vfs::Status> Stat = VFS->status(Filename);
101     if (!Stat)
102       return errorCodeToError(Stat.getError());
103     if (Timestamp != std::chrono::time_point_cast<std::chrono::seconds>(
104                          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(MemBuffer->getMemBufferRef());
122   if (!ErrOrFat) {
123     consumeError(ErrOrFat.takeError());
124     ObjectBuffers.push_back(MemBuffer->getMemBufferRef());
125   } else {
126     FatBinary = std::move(*ErrOrFat);
127     FatBinaryName = std::string(Filename);
128     ObjectBuffers =
129         getMachOFatMemoryBuffers(FatBinaryName, *MemBuffer, *FatBinary);
130   }
131 
132   Objects.reserve(ObjectBuffers.size());
133   for (auto MemRef : ObjectBuffers) {
134     auto ErrOrObjectFile = object::ObjectFile::createObjectFile(MemRef);
135     if (!ErrOrObjectFile)
136       return ErrOrObjectFile.takeError();
137     Objects.push_back(std::move(*ErrOrObjectFile));
138   }
139 
140   return Error::success();
141 }
142 
143 std::vector<const object::ObjectFile *>
getObjects() const144 BinaryHolder::ObjectEntry::getObjects() const {
145   std::vector<const object::ObjectFile *> Result;
146   Result.reserve(Objects.size());
147   for (auto &Object : Objects) {
148     Result.push_back(Object.get());
149   }
150   return Result;
151 }
152 Expected<const object::ObjectFile &>
getObject(const Triple & T) const153 BinaryHolder::ObjectEntry::getObject(const Triple &T) const {
154   for (const auto &Obj : Objects) {
155     if (const auto *MachO = dyn_cast<object::MachOObjectFile>(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(object::object_error::arch_not_found);
162 }
163 
164 Expected<const BinaryHolder::ObjectEntry &>
getObjectEntry(StringRef Filename,TimestampTy Timestamp,bool Verbose)165 BinaryHolder::ArchiveEntry::getObjectEntry(StringRef Filename,
166                                            TimestampTy Timestamp,
167                                            bool Verbose) {
168   StringRef ArchiveFilename;
169   StringRef ObjectFilename;
170   std::tie(ArchiveFilename, 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(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 (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                                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(*ErrOrMem);
213           if (!ErrOrObjectFile)
214             return ErrOrObjectFile.takeError();
215 
216           OE->Objects.push_back(std::move(*ErrOrObjectFile));
217         }
218       }
219     }
220     if (Err)
221       return std::move(Err);
222   }
223 
224   if (OE->Objects.empty())
225     return errorCodeToError(errc::no_such_file_or_directory);
226 
227   MemberCache[Key] = std::move(OE);
228   return *MemberCache[Key];
229 }
230 
231 Expected<const BinaryHolder::ObjectEntry &>
getObjectEntry(StringRef Filename,TimestampTy Timestamp)232 BinaryHolder::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     if (ArchiveCache.count(ArchiveFilename)) {
242       return ArchiveCache[ArchiveFilename]->getObjectEntry(Filename, Timestamp,
243                                                            Verbose);
244     } else {
245       auto AE = std::make_unique<ArchiveEntry>();
246       auto Err = AE->load(VFS, Filename, Timestamp, Verbose);
247       if (Err) {
248         // Don't return the error here: maybe the file wasn't an archive.
249         llvm::consumeError(std::move(Err));
250       } else {
251         ArchiveCache[ArchiveFilename] = std::move(AE);
252         return ArchiveCache[ArchiveFilename]->getObjectEntry(
253             Filename, Timestamp, Verbose);
254       }
255     }
256   }
257 
258   // If this is an object, we might have it cached. If not we'll have to load
259   // it from the file system and cache it now.
260   std::lock_guard<std::mutex> Lock(ObjectCacheMutex);
261   if (!ObjectCache.count(Filename)) {
262     auto OE = std::make_unique<ObjectEntry>();
263     auto Err = OE->load(VFS, Filename, Timestamp, Verbose);
264     if (Err)
265       return std::move(Err);
266     ObjectCache[Filename] = std::move(OE);
267   }
268 
269   return *ObjectCache[Filename];
270 }
271 
clear()272 void BinaryHolder::clear() {
273   std::lock_guard<std::mutex> ArchiveLock(ArchiveCacheMutex);
274   std::lock_guard<std::mutex> ObjectLock(ObjectCacheMutex);
275   ArchiveCache.clear();
276   ObjectCache.clear();
277 }
278 
279 } // namespace dsymutil
280 } // namespace llvm
281