303 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// This program is a utility that aims to be a dropin replacement for10// 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 19namespace llvm {20namespace dsymutil {21 22static std::pair<StringRef, StringRef>23getArchiveAndObjectName(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 29static bool isArchive(StringRef Filename) { return Filename.ends_with(")"); }30 31static std::vector<MemoryBufferRef>32getMachOFatMemoryBuffers(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 44BinaryHolder::BinaryHolder(IntrusiveRefCntPtr<vfs::FileSystem> VFS,45 BinaryHolder::Options Opts)46 : VFS(VFS), Opts(Opts) {}47 48Error 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(ArchiveFilename, -1, false);57 if (auto Err = ErrOrBuff.getError())58 return errorCodeToError(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 with66 // a fat binary.67 std::vector<MemoryBufferRef> ArchiveBuffers;68 69 auto ErrOrFat =70 object::MachOUniversalBinary::create(MemBuffer->getMemBufferRef());71 if (!ErrOrFat) {72 consumeError(ErrOrFat.takeError());73 ArchiveBuffers.push_back(MemBuffer->getMemBufferRef());74 } else {75 FatBinary = std::move(*ErrOrFat);76 FatBinaryName = std::string(ArchiveFilename);77 ArchiveBuffers =78 getMachOFatMemoryBuffers(FatBinaryName, *MemBuffer, *FatBinary);79 }80 81 // Finally, try to load the archives.82 Archives.reserve(ArchiveBuffers.size());83 for (auto MemRef : ArchiveBuffers) {84 auto ErrOrArchive = object::Archive::create(MemRef);85 if (!ErrOrArchive)86 return ErrOrArchive.takeError();87 Archives.push_back(std::move(*ErrOrArchive));88 }89 90 return Error::success();91}92 93Error 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(Filename, -1, false);100 if (auto Err = ErrOrBuff.getError())101 return errorCodeToError(Err);102 103 if (Opts.Warn && Filename != "-" && Timestamp != sys::TimePoint<>()) {104 llvm::ErrorOr<vfs::Status> Stat = VFS->status(Filename);105 if (!Stat)106 return errorCodeToError(Stat.getError());107 if (Timestamp != std::chrono::time_point_cast<std::chrono::seconds>(108 Stat->getLastModificationTime()))109 WithColor::warning() << Filename110 << ": 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 a121 // fat binary.122 std::vector<MemoryBufferRef> ObjectBuffers;123 124 auto ErrOrFat =125 object::MachOUniversalBinary::create(MemBuffer->getMemBufferRef());126 if (!ErrOrFat) {127 consumeError(ErrOrFat.takeError());128 ObjectBuffers.push_back(MemBuffer->getMemBufferRef());129 } else {130 FatBinary = std::move(*ErrOrFat);131 FatBinaryName = std::string(Filename);132 ObjectBuffers =133 getMachOFatMemoryBuffers(FatBinaryName, *MemBuffer, *FatBinary);134 }135 136 Objects.reserve(ObjectBuffers.size());137 for (auto MemRef : ObjectBuffers) {138 auto ErrOrObjectFile = object::ObjectFile::createObjectFile(MemRef);139 if (!ErrOrObjectFile)140 return ErrOrObjectFile.takeError();141 Objects.push_back(std::move(*ErrOrObjectFile));142 }143 144 return Error::success();145}146 147std::vector<const object::ObjectFile *>148BinaryHolder::ObjectEntry::getObjects() const {149 std::vector<const object::ObjectFile *> Result;150 Result.reserve(Objects.size());151 for (auto &Object : Objects) {152 Result.push_back(Object.get());153 }154 return Result;155}156Expected<const object::ObjectFile &>157BinaryHolder::ObjectEntry::getObject(const Triple &T) const {158 for (const auto &Obj : Objects) {159 if (const auto *MachO = dyn_cast<object::MachOObjectFile>(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(object::object_error::arch_not_found);166}167 168Expected<const BinaryHolder::ObjectEntry &>169BinaryHolder::ArchiveEntry::getObjectEntry(StringRef Filename,170 TimestampTy Timestamp,171 Options Opts) {172 StringRef ArchiveFilename;173 StringRef ObjectFilename;174 std::tie(ArchiveFilename, 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(Key); It != MemberCache.end())180 return *It->second;181 182 // Create a new ObjectEntry, but don't add it to the cache yet. Loading of183 // the archive members might fail and we don't want to lock the whole archive184 // 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 ModTimeOrErr.get())) {199 if (Opts.Verbose)200 WithColor::warning()201 << *NameOrErr202 << ": timestamp mismatch between archive member ("203 << ModTimeOrErr.get() << ") and debug map (" << Timestamp204 << ")\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(*ErrOrMem);217 if (!ErrOrObjectFile)218 return ErrOrObjectFile.takeError();219 220 OE->Objects.push_back(std::move(*ErrOrObjectFile));221 }222 }223 }224 if (Err)225 return std::move(Err);226 }227 228 if (OE->Objects.empty())229 return errorCodeToError(errc::no_such_file_or_directory);230 231 return *(MemberCache[Key] = std::move(OE));232}233 234Expected<const BinaryHolder::ObjectEntry &>235BinaryHolder::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 archive240 // 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(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(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 load263 // it from the file system and cache it now.264 std::lock_guard<std::mutex> Lock(ObjectCacheMutex);265 ObjectRefCounter[Filename]++;266 if (!ObjectCache.count(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 277void 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 284void 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(ArchiveFilename);293 return;294 }295 296 std::lock_guard<std::mutex> Lock(ObjectCacheMutex);297 if (--ObjectRefCounter[Filename] == 0)298 ObjectCache.erase(Filename);299}300 301} // namespace dsymutil302} // namespace llvm303