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.endswith(")"); } 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 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 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 *> 144 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 &> 153 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 &> 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 172 // Try the cache first. 173 KeyTy Key = {ObjectFilename, Timestamp}; 174 175 { 176 std::lock_guard<std::mutex> Lock(MemberCacheMutex); 177 if (MemberCache.count(Key)) 178 return MemberCache[Key]; 179 } 180 181 // Create a new ObjectEntry, but don't add it to the cache yet. Loading of 182 // the archive members might fail and we don't want to lock the whole archive 183 // during this operation. 184 ObjectEntry OE; 185 186 for (const auto &Archive : Archives) { 187 Error Err = Error::success(); 188 for (auto Child : Archive->children(Err)) { 189 if (auto NameOrErr = Child.getName()) { 190 if (*NameOrErr == ObjectFilename) { 191 auto ModTimeOrErr = Child.getLastModified(); 192 if (!ModTimeOrErr) 193 return ModTimeOrErr.takeError(); 194 195 if (Timestamp != sys::TimePoint<>() && 196 Timestamp != std::chrono::time_point_cast<std::chrono::seconds>( 197 ModTimeOrErr.get())) { 198 if (Verbose) 199 WithColor::warning() 200 << *NameOrErr 201 << ": timestamp mismatch between archive member (" 202 << ModTimeOrErr.get() << ") and debug map (" << Timestamp 203 << ")\n"; 204 continue; 205 } 206 207 if (Verbose) 208 WithColor::note() << "found member in archive.\n"; 209 210 auto ErrOrMem = Child.getMemoryBufferRef(); 211 if (!ErrOrMem) 212 return ErrOrMem.takeError(); 213 214 auto ErrOrObjectFile = 215 object::ObjectFile::createObjectFile(*ErrOrMem); 216 if (!ErrOrObjectFile) 217 return ErrOrObjectFile.takeError(); 218 219 OE.Objects.push_back(std::move(*ErrOrObjectFile)); 220 } 221 } 222 } 223 if (Err) 224 return std::move(Err); 225 } 226 227 if (OE.Objects.empty()) 228 return errorCodeToError(errc::no_such_file_or_directory); 229 230 std::lock_guard<std::mutex> Lock(MemberCacheMutex); 231 MemberCache.try_emplace(Key, std::move(OE)); 232 return MemberCache[Key]; 233 } 234 235 Expected<const BinaryHolder::ObjectEntry &> 236 BinaryHolder::getObjectEntry(StringRef Filename, TimestampTy Timestamp) { 237 if (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 if (ArchiveCache.count(ArchiveFilename)) { 246 return ArchiveCache[ArchiveFilename].getObjectEntry(Filename, Timestamp, 247 Verbose); 248 } else { 249 ArchiveEntry &AE = ArchiveCache[ArchiveFilename]; 250 auto Err = AE.load(VFS, Filename, Timestamp, Verbose); 251 if (Err) { 252 ArchiveCache.erase(ArchiveFilename); 253 // Don't return the error here: maybe the file wasn't an archive. 254 llvm::consumeError(std::move(Err)); 255 } else { 256 return ArchiveCache[ArchiveFilename].getObjectEntry(Filename, Timestamp, 257 Verbose); 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 if (!ObjectCache.count(Filename)) { 266 ObjectEntry &OE = ObjectCache[Filename]; 267 auto Err = OE.load(VFS, Filename, Timestamp, Verbose); 268 if (Err) { 269 ObjectCache.erase(Filename); 270 return std::move(Err); 271 } 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 } // namespace dsymutil 285 } // namespace llvm 286