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(Start: 0, N: Filename.rfind(C: '(')); |
25 | StringRef Object = Filename.substr(Start: Archive.size() + 1).drop_back(); |
26 | return {Archive, Object}; |
27 | } |
28 | |
29 | static bool isArchive(StringRef Filename) { return Filename.ends_with(Suffix: ")" ); } |
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(Start: It->getOffset(), N: It->getSize()); |
39 | Buffers.emplace_back(args&: ObjData, args&: 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(Name: ArchiveFilename, FileSize: -1, RequiresNullTerminator: false); |
57 | if (auto Err = ErrOrBuff.getError()) |
58 | return errorCodeToError(EC: 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(Source: MemBuffer->getMemBufferRef()); |
71 | if (!ErrOrFat) { |
72 | consumeError(Err: ErrOrFat.takeError()); |
73 | ArchiveBuffers.push_back(x: MemBuffer->getMemBufferRef()); |
74 | } else { |
75 | FatBinary = std::move(*ErrOrFat); |
76 | FatBinaryName = std::string(ArchiveFilename); |
77 | ArchiveBuffers = |
78 | getMachOFatMemoryBuffers(Filename: FatBinaryName, Mem&: *MemBuffer, Fat&: *FatBinary); |
79 | } |
80 | |
81 | // Finally, try to load the archives. |
82 | Archives.reserve(n: ArchiveBuffers.size()); |
83 | for (auto MemRef : ArchiveBuffers) { |
84 | auto ErrOrArchive = object::Archive::create(Source: MemRef); |
85 | if (!ErrOrArchive) |
86 | return ErrOrArchive.takeError(); |
87 | Archives.push_back(x: 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(Name: Filename, FileSize: -1, RequiresNullTerminator: false); |
100 | if (auto Err = ErrOrBuff.getError()) |
101 | return errorCodeToError(EC: Err); |
102 | |
103 | if (Opts.Warn && Filename != "-" && Timestamp != sys::TimePoint<>()) { |
104 | llvm::ErrorOr<vfs::Status> Stat = VFS->status(Path: Filename); |
105 | if (!Stat) |
106 | return errorCodeToError(EC: Stat.getError()); |
107 | if (Timestamp != std::chrono::time_point_cast<std::chrono::seconds>( |
108 | t: 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(Source: MemBuffer->getMemBufferRef()); |
126 | if (!ErrOrFat) { |
127 | consumeError(Err: ErrOrFat.takeError()); |
128 | ObjectBuffers.push_back(x: MemBuffer->getMemBufferRef()); |
129 | } else { |
130 | FatBinary = std::move(*ErrOrFat); |
131 | FatBinaryName = std::string(Filename); |
132 | ObjectBuffers = |
133 | getMachOFatMemoryBuffers(Filename: FatBinaryName, Mem&: *MemBuffer, Fat&: *FatBinary); |
134 | } |
135 | |
136 | Objects.reserve(n: ObjectBuffers.size()); |
137 | for (auto MemRef : ObjectBuffers) { |
138 | auto ErrOrObjectFile = object::ObjectFile::createObjectFile(Object: MemRef); |
139 | if (!ErrOrObjectFile) |
140 | return ErrOrObjectFile.takeError(); |
141 | Objects.push_back(x: 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(n: Objects.size()); |
151 | for (auto &Object : Objects) { |
152 | Result.push_back(x: 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>(Val: 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(EC: 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(args&: ArchiveFilename, args&: ObjectFilename) = getArchiveAndObjectName(Filename); |
175 | KeyTy Key = {ObjectFilename, Timestamp}; |
176 | |
177 | // Try the cache first. |
178 | std::lock_guard<std::mutex> Lock(MemberCacheMutex); |
179 | if (auto It = MemberCache.find(Val: Key); It != MemberCache.end()) |
180 | return *It->second; |
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 | t: 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(Object: *ErrOrMem); |
217 | if (!ErrOrObjectFile) |
218 | return ErrOrObjectFile.takeError(); |
219 | |
220 | OE->Objects.push_back(x: std::move(*ErrOrObjectFile)); |
221 | } |
222 | } |
223 | } |
224 | if (Err) |
225 | return std::move(Err); |
226 | } |
227 | |
228 | if (OE->Objects.empty()) |
229 | return errorCodeToError(EC: errc::no_such_file_or_directory); |
230 | |
231 | return *(MemberCache[Key] = std::move(OE)); |
232 | } |
233 | |
234 | Expected<const BinaryHolder::ObjectEntry &> |
235 | BinaryHolder::getObjectEntry(StringRef Filename, TimestampTy Timestamp) { |
236 | if (Opts.Verbose) |
237 | WithColor::note() << "trying to open '" << Filename << "'\n" ; |
238 | |
239 | // If this is an archive, we might have either the object or the archive |
240 | // cached. In this case we can load it without accessing the file system. |
241 | if (isArchive(Filename)) { |
242 | StringRef ArchiveFilename = getArchiveAndObjectName(Filename).first; |
243 | std::lock_guard<std::mutex> Lock(ArchiveCacheMutex); |
244 | ArchiveRefCounter[ArchiveFilename]++; |
245 | if (auto It = ArchiveCache.find(Key: ArchiveFilename); |
246 | It != ArchiveCache.end()) { |
247 | return It->second->getObjectEntry(Filename, Timestamp, Opts); |
248 | } else { |
249 | auto AE = std::make_unique<ArchiveEntry>(); |
250 | auto Err = AE->load(VFS, Filename, Timestamp, Opts); |
251 | if (Err) { |
252 | // Don't return the error here: maybe the file wasn't an archive. |
253 | llvm::consumeError(Err: std::move(Err)); |
254 | } else { |
255 | auto &Cache = ArchiveCache[ArchiveFilename]; |
256 | Cache = std::move(AE); |
257 | return Cache->getObjectEntry(Filename, Timestamp, Opts); |
258 | } |
259 | } |
260 | } |
261 | |
262 | // If this is an object, we might have it cached. If not we'll have to load |
263 | // it from the file system and cache it now. |
264 | std::lock_guard<std::mutex> Lock(ObjectCacheMutex); |
265 | ObjectRefCounter[Filename]++; |
266 | if (!ObjectCache.count(Key: Filename)) { |
267 | auto OE = std::make_unique<ObjectEntry>(); |
268 | auto Err = OE->load(VFS, Filename, Timestamp, Opts); |
269 | if (Err) |
270 | return std::move(Err); |
271 | ObjectCache[Filename] = std::move(OE); |
272 | } |
273 | |
274 | return *ObjectCache[Filename]; |
275 | } |
276 | |
277 | void BinaryHolder::clear() { |
278 | std::lock_guard<std::mutex> ArchiveLock(ArchiveCacheMutex); |
279 | std::lock_guard<std::mutex> ObjectLock(ObjectCacheMutex); |
280 | ArchiveCache.clear(); |
281 | ObjectCache.clear(); |
282 | } |
283 | |
284 | void BinaryHolder::eraseObjectEntry(StringRef Filename) { |
285 | if (Opts.Verbose) |
286 | WithColor::note() << "erasing '" << Filename << "' from cache\n" ; |
287 | |
288 | if (isArchive(Filename)) { |
289 | StringRef ArchiveFilename = getArchiveAndObjectName(Filename).first; |
290 | std::lock_guard<std::mutex> Lock(ArchiveCacheMutex); |
291 | if (--ArchiveRefCounter[ArchiveFilename] == 0) |
292 | ArchiveCache.erase(Key: ArchiveFilename); |
293 | return; |
294 | } |
295 | |
296 | std::lock_guard<std::mutex> Lock(ObjectCacheMutex); |
297 | if (--ObjectRefCounter[Filename] == 0) |
298 | ObjectCache.erase(Key: Filename); |
299 | } |
300 | |
301 | } // namespace dsymutil |
302 | } // namespace llvm |
303 | |