| 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 | |