Files
swift-mirror/include/swift/RemoteInspection/ReflectionContext.h
Augusto Noronha e2c8b761cd [NFC][RemoteInspection] Add an opaque AddressSpace field to RemoteAddress
Add an extra opaque field to AddressSpace, which can be used by clients
of RemoteInspection to distinguish between different address spaces.

LLDB employs an optimization where it reads memory from files instead of
the running process whenever it can to speed up memory reads (these can
be slow when debugging something over a network). To do this, it needs
to keep track whether an address originated from a process or a file. It
currently distinguishes addresses by setting an unused high bit on the
address, but because of pointer authentication this is not a reliable
solution. In order to keep this optimization working, this patch adds an
extra opaque AddressSpace field to RemoteAddress, which LLDB can use on
its own implementation of MemoryReader to distinguish between addresses.

This patch is NFC for the other RemoteInspection clients, as it adds
extra information to RemoteAddress, which is entirely optional and if
unused should not change the behavior of the library.

Although this patch is quite big the changes are largely mechanical,
replacing threading StoredPointer with RemoteAddress.

rdar://148361743
(cherry picked from commit 58df5534d2)
(cherry picked from commit 8f3862b5e7)
2025-07-11 16:36:40 -07:00

2228 lines
85 KiB
C++

//===--- ReflectionContext.h - Swift Type Reflection Context ----*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Implements the context for reflection of values in the address space of a
// remote process.
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_REFLECTION_REFLECTIONCONTEXT_H
#define SWIFT_REFLECTION_REFLECTIONCONTEXT_H
#include "llvm/BinaryFormat/COFF.h"
#include "llvm/BinaryFormat/MachO.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Object/COFF.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/Memory.h"
#include "llvm/ADT/STLExtras.h"
#include "swift/ABI/Enum.h"
#include "swift/ABI/ObjectFile.h"
#include "swift/Concurrency/Actor.h"
#include "swift/Remote/MemoryReader.h"
#include "swift/Remote/MetadataReader.h"
#include "swift/RemoteInspection/DescriptorFinder.h"
#include "swift/RemoteInspection/GenericMetadataCacheEntry.h"
#include "swift/RemoteInspection/Records.h"
#include "swift/RemoteInspection/RuntimeInternals.h"
#include "swift/RemoteInspection/TypeLowering.h"
#include "swift/RemoteInspection/TypeRef.h"
#include "swift/RemoteInspection/TypeRefBuilder.h"
#include "swift/Basic/Unreachable.h"
#include <set>
#include <utility>
#include <vector>
#include <inttypes.h>
// The Swift runtime can be built in two ways: with or without
// SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION enabled. In order to decode the
// lock used in a runtime with priority escalation enabled, we need inline
// functions from dispatch/swift_concurrency_private.h. If we don't have that
// header at build time, we can still build but we'll be unable to decode the
// lock and thus information about a running task is degraded. There are four
// combinations:
//
// Runtime | swift_concurrency_private.h | task running info
// --------------------+-----------------------------+------------------
// without escalation | present | full
// without escalation | not present | full
// with escalation | present | full
// with escalation | not present | DEGRADED
//
// Currently, degraded info has these effects:
// 1. Task.IsRunning is not available, indicated with Task.HasIsRunning = false.
// 2. Task async backtraces are not provided.
// 3. Task and actor thread ports are not available, indicated with
// HasThreadPort = false.
#if __has_include(<dispatch/swift_concurrency_private.h>)
#include <dispatch/swift_concurrency_private.h>
#define HAS_DISPATCH_LOCK_IS_LOCKED 1
#endif
namespace {
template <unsigned PointerSize> struct MachOTraits;
template <> struct MachOTraits<4> {
using Header = const struct llvm::MachO::mach_header;
using SegmentCmd = const struct llvm::MachO::segment_command;
using Section = const struct llvm::MachO::section;
static constexpr size_t MagicNumber = llvm::MachO::MH_MAGIC;
};
template <> struct MachOTraits<8> {
using Header = const struct llvm::MachO::mach_header_64;
using SegmentCmd = const struct llvm::MachO::segment_command_64;
using Section = const struct llvm::MachO::section_64;
static constexpr size_t MagicNumber = llvm::MachO::MH_MAGIC_64;
};
template <unsigned char ELFClass> struct ELFTraits;
template <> struct ELFTraits<llvm::ELF::ELFCLASS32> {
using Header = const struct llvm::ELF::Elf32_Ehdr;
using Section = const struct llvm::ELF::Elf32_Shdr;
using Offset = llvm::ELF::Elf32_Off;
using Size = llvm::ELF::Elf32_Word;
static constexpr unsigned char ELFClass = llvm::ELF::ELFCLASS32;
};
template <> struct ELFTraits<llvm::ELF::ELFCLASS64> {
using Header = const struct llvm::ELF::Elf64_Ehdr;
using Section = const struct llvm::ELF::Elf64_Shdr;
using Offset = llvm::ELF::Elf64_Off;
using Size = llvm::ELF::Elf64_Xword;
static constexpr unsigned char ELFClass = llvm::ELF::ELFCLASS64;
};
} // namespace
namespace swift {
namespace reflection {
using swift::remote::MemoryReader;
using swift::remote::RemoteAddress;
template <typename Runtime>
class ReflectionContext
: public remote::MetadataReader<Runtime, TypeRefBuilder> {
using super = remote::MetadataReader<Runtime, TypeRefBuilder>;
using super::readMetadata;
using super::readObjCClassName;
using super::readResolvedPointerValue;
llvm::DenseMap<std::pair<RemoteAddress, remote::TypeInfoProvider::IdType>,
const RecordTypeInfo *>
Cache;
/// All buffers we need to keep around long term. This will automatically free them
/// when this object is destroyed.
std::vector<MemoryReader::ReadBytesResult> savedBuffers;
std::vector<std::tuple<RemoteAddress, RemoteAddress>> textRanges;
std::vector<std::tuple<RemoteAddress, RemoteAddress>> dataRanges;
bool setupTargetPointers = false;
typename super::StoredPointer target_asyncTaskMetadata = 0;
typename super::StoredPointer target_non_future_adapter = 0;
typename super::StoredPointer target_future_adapter = 0;
typename super::StoredPointer target_task_wait_throwing_resume_adapter = 0;
typename super::StoredPointer target_task_future_wait_resume_adapter = 0;
bool supportsPriorityEscalation = false;
typename super::StoredSize asyncTaskSize = 0;
public:
using super::getBuilder;
using super::readDemanglingForContextDescriptor;
using super::readGenericArgFromMetadata;
using super::readIsaMask;
using super::readMetadataAndValueErrorExistential;
using super::readMetadataAndValueOpaqueExistential;
using super::readMetadataFromInstance;
using super::readTypeFromMetadata;
using super::stripSignedPointer;
using typename super::StoredPointer;
using typename super::StoredSignedPointer;
using typename super::StoredSize;
struct AsyncTaskAllocationChunk {
enum class ChunkKind {
Unknown,
NonPointer,
RawPointer,
StrongReference,
UnownedReference,
WeakReference,
UnmanagedReference
};
StoredPointer Start;
unsigned Length;
ChunkKind Kind;
};
struct AsyncTaskSlabInfo {
StoredPointer NextSlab;
StoredSize SlabSize;
std::vector<AsyncTaskAllocationChunk> Chunks;
};
struct AsyncTaskInfo {
// Job flags.
unsigned Kind;
unsigned EnqueuePriority;
bool IsChildTask;
bool IsFuture;
bool IsGroupChildTask;
bool IsAsyncLetTask;
bool IsSynchronousStartTask;
// Task flags.
unsigned MaxPriority;
bool IsCancelled;
bool IsStatusRecordLocked;
bool IsEscalated;
bool HasIsRunning; // If false, the IsRunning flag is not valid.
bool IsRunning;
bool IsEnqueued;
bool IsComplete;
bool HasThreadPort;
uint32_t ThreadPort;
uint64_t Id;
StoredPointer RunJob;
StoredPointer AllocatorSlabPtr;
std::vector<StoredPointer> ChildTasks;
std::vector<StoredPointer> AsyncBacktraceFrames;
StoredPointer ResumeAsyncContext;
};
struct ActorInfo {
StoredPointer FirstJob;
uint8_t State;
bool IsPriorityEscalated;
bool IsDistributedRemote;
uint8_t MaxPriority;
bool HasThreadPort;
uint32_t ThreadPort;
};
explicit ReflectionContext(
std::shared_ptr<MemoryReader> reader,
remote::ExternalTypeRefCache *externalCache = nullptr,
reflection::DescriptorFinder *descriptorFinder = nullptr)
: super(std::move(reader), *this, externalCache, descriptorFinder) {}
ReflectionContext(const ReflectionContext &other) = delete;
ReflectionContext &operator=(const ReflectionContext &other) = delete;
MemoryReader &getReader() {
return *this->Reader;
}
unsigned getSizeOfHeapObject() {
// This must match sizeof(HeapObject) for the target.
return sizeof(StoredPointer) * 2;
}
/// On success returns the ID of the newly registered Reflection Info.
template <typename T>
std::optional<uint32_t> readMachOSections(
RemoteAddress ImageStart,
llvm::SmallVector<llvm::StringRef, 1> PotentialModuleNames = {}) {
auto Buf =
this->getReader().readBytes(ImageStart, sizeof(typename T::Header));
if (!Buf)
return {};
auto Header = reinterpret_cast<typename T::Header *>(Buf.get());
assert(Header->magic == T::MagicNumber && "invalid MachO file");
auto NumCommands = Header->sizeofcmds;
// The layout of the executable is such that the commands immediately follow
// the header.
auto CmdStartAddress = ImageStart + sizeof(typename T::Header);
uint32_t SegmentCmdHdrSize = sizeof(typename T::SegmentCmd);
uint64_t Offset = 0;
// Find the __TEXT segment.
typename T::SegmentCmd *TextCommand = nullptr;
for (unsigned I = 0; I < NumCommands; ++I) {
auto CmdBuf = this->getReader().readBytes(CmdStartAddress + Offset,
SegmentCmdHdrSize);
if (!CmdBuf)
return {};
auto CmdHdr = reinterpret_cast<typename T::SegmentCmd *>(CmdBuf.get());
if (strncmp(CmdHdr->segname, "__TEXT", sizeof(CmdHdr->segname)) == 0) {
TextCommand = CmdHdr;
savedBuffers.push_back(std::move(CmdBuf));
break;
}
Offset += CmdHdr->cmdsize;
}
// No __TEXT segment, bail out.
if (!TextCommand)
return {};
// Find the load command offset.
auto loadCmdOffset = ImageStart + Offset + sizeof(typename T::Header);
// Read the load command.
auto LoadCmdBuf = this->getReader().readBytes(
loadCmdOffset, sizeof(typename T::SegmentCmd));
if (!LoadCmdBuf)
return {};
auto LoadCmd = reinterpret_cast<typename T::SegmentCmd *>(LoadCmdBuf.get());
// The sections start immediately after the load command.
unsigned NumSect = LoadCmd->nsects;
auto SectAddress = loadCmdOffset + sizeof(typename T::SegmentCmd);
auto Sections = this->getReader().readBytes(
SectAddress, NumSect * sizeof(typename T::Section));
if (!Sections)
return {};
auto Slide = ImageStart - TextCommand->vmaddr;
auto SectionsBuf = reinterpret_cast<const char *>(Sections.get());
auto findMachOSectionByName = [&](llvm::StringRef Name)
-> std::pair<RemoteRef<void>, uint64_t> {
for (unsigned I = 0; I < NumSect; ++I) {
auto S = reinterpret_cast<typename T::Section *>(
SectionsBuf + (I * sizeof(typename T::Section)));
if (strncmp(S->sectname, Name.data(), sizeof(S->sectname)) != 0)
continue;
auto RemoteSecStart = Slide + S->addr;
auto LocalSectBuf =
this->getReader().readBytes(RemoteSecStart, S->size);
if (!LocalSectBuf)
return {nullptr, 0};
auto StartRef = RemoteRef<void>(RemoteSecStart, LocalSectBuf.get());
savedBuffers.push_back(std::move(LocalSectBuf));
return {StartRef, S->size};
}
return {nullptr, 0};
};
SwiftObjectFileFormatMachO ObjectFileFormat;
auto FieldMdSec = findMachOSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::fieldmd));
auto AssocTySec = findMachOSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::assocty));
auto BuiltinTySec = findMachOSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::builtin));
auto CaptureSec = findMachOSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::capture));
auto TypeRefMdSec = findMachOSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::typeref));
auto ReflStrMdSec = findMachOSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::reflstr));
auto ConformMdSec = findMachOSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::conform));
auto MPEnumMdSec = findMachOSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::mpenum));
if (FieldMdSec.first == nullptr &&
AssocTySec.first == nullptr &&
BuiltinTySec.first == nullptr &&
CaptureSec.first == nullptr &&
TypeRefMdSec.first == nullptr &&
ReflStrMdSec.first == nullptr &&
ConformMdSec.first == nullptr &&
MPEnumMdSec.first == nullptr)
return {};
ReflectionInfo info = {{FieldMdSec.first, FieldMdSec.second},
{AssocTySec.first, AssocTySec.second},
{BuiltinTySec.first, BuiltinTySec.second},
{CaptureSec.first, CaptureSec.second},
{TypeRefMdSec.first, TypeRefMdSec.second},
{ReflStrMdSec.first, ReflStrMdSec.second},
{ConformMdSec.first, ConformMdSec.second},
{MPEnumMdSec.first, MPEnumMdSec.second},
PotentialModuleNames};
auto InfoID = this->addReflectionInfo(info);
auto TextSegmentStart = Slide + TextCommand->vmaddr;
auto TextSegmentEnd = TextSegmentStart + TextCommand->vmsize;
textRanges.push_back(std::make_tuple(TextSegmentStart, TextSegmentEnd));
// Find the __DATA segments.
for (unsigned I = 0; I < NumCommands; ++I) {
auto CmdBuf = this->getReader().readBytes(CmdStartAddress + Offset,
SegmentCmdHdrSize);
if (!CmdBuf)
return {};
auto CmdHdr = reinterpret_cast<typename T::SegmentCmd *>(CmdBuf.get());
// Look for any segment name starting with __DATA or __AUTH.
if (strncmp(CmdHdr->segname, "__DATA", 6) == 0 ||
strncmp(CmdHdr->segname, "__AUTH", 6) == 0) {
auto DataSegmentStart = Slide + CmdHdr->vmaddr;
auto DataSegmentEnd = DataSegmentStart + CmdHdr->vmsize;
assert(DataSegmentStart > ImageStart &&
"invalid range for __DATA/__AUTH");
dataRanges.push_back(std::make_tuple(DataSegmentStart, DataSegmentEnd));
}
Offset += CmdHdr->cmdsize;
}
savedBuffers.push_back(std::move(Buf));
savedBuffers.push_back(std::move(Sections));
return InfoID;
}
/// On success returns the ID of the newly registered Reflection Info.
std::optional<uint32_t> readPECOFFSections(
RemoteAddress ImageStart,
llvm::SmallVector<llvm::StringRef, 1> PotentialModuleNames = {}) {
auto DOSHdrBuf = this->getReader().readBytes(
ImageStart, sizeof(llvm::object::dos_header));
if (!DOSHdrBuf)
return {};
auto DOSHdr =
reinterpret_cast<const llvm::object::dos_header *>(DOSHdrBuf.get());
auto COFFFileHdrAddr = ImageStart + DOSHdr->AddressOfNewExeHeader +
sizeof(llvm::COFF::PEMagic);
auto COFFFileHdrBuf = this->getReader().readBytes(
COFFFileHdrAddr, sizeof(llvm::object::coff_file_header));
if (!COFFFileHdrBuf)
return {};
auto COFFFileHdr = reinterpret_cast<const llvm::object::coff_file_header *>(
COFFFileHdrBuf.get());
auto SectionTableAddr = COFFFileHdrAddr +
sizeof(llvm::object::coff_file_header) +
COFFFileHdr->SizeOfOptionalHeader;
auto SectionTableBuf = this->getReader().readBytes(
SectionTableAddr,
sizeof(llvm::object::coff_section) * COFFFileHdr->NumberOfSections);
if (!SectionTableBuf)
return {};
auto findCOFFSectionByName =
[&](llvm::StringRef Name) -> std::pair<RemoteRef<void>, uint64_t> {
for (size_t i = 0; i < COFFFileHdr->NumberOfSections; ++i) {
const llvm::object::coff_section *COFFSec =
reinterpret_cast<const llvm::object::coff_section *>(
SectionTableBuf.get()) +
i;
llvm::StringRef SectionName =
(COFFSec->Name[llvm::COFF::NameSize - 1] == 0)
? COFFSec->Name
: llvm::StringRef(COFFSec->Name, llvm::COFF::NameSize);
if (SectionName != Name)
continue;
auto Addr = ImageStart + COFFSec->VirtualAddress;
auto Buf = this->getReader().readBytes(Addr, COFFSec->VirtualSize);
if (!Buf)
return {nullptr, 0};
auto BufStart = Buf.get();
savedBuffers.push_back(std::move(Buf));
auto Begin = RemoteRef<void>(Addr, BufStart);
auto Size = COFFSec->VirtualSize;
// FIXME: This code needs to be cleaned up and updated
// to make it work for 32 bit platforms.
Begin = Begin.atByteOffset(8);
Size -= 16;
return {Begin, Size};
}
return {nullptr, 0};
};
SwiftObjectFileFormatCOFF ObjectFileFormat;
auto FieldMdSec = findCOFFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::fieldmd));
auto AssocTySec = findCOFFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::assocty));
auto BuiltinTySec = findCOFFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::builtin));
auto CaptureSec = findCOFFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::capture));
auto TypeRefMdSec = findCOFFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::typeref));
auto ReflStrMdSec = findCOFFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::reflstr));
auto ConformMdSec = findCOFFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::conform));
auto MPEnumMdSec = findCOFFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::mpenum));
if (FieldMdSec.first == nullptr &&
AssocTySec.first == nullptr &&
BuiltinTySec.first == nullptr &&
CaptureSec.first == nullptr &&
TypeRefMdSec.first == nullptr &&
ReflStrMdSec.first == nullptr &&
ConformMdSec.first == nullptr &&
MPEnumMdSec.first == nullptr)
return {};
ReflectionInfo Info = {{FieldMdSec.first, FieldMdSec.second},
{AssocTySec.first, AssocTySec.second},
{BuiltinTySec.first, BuiltinTySec.second},
{CaptureSec.first, CaptureSec.second},
{TypeRefMdSec.first, TypeRefMdSec.second},
{ReflStrMdSec.first, ReflStrMdSec.second},
{ConformMdSec.first, ConformMdSec.second},
{MPEnumMdSec.first, MPEnumMdSec.second},
PotentialModuleNames};
return this->addReflectionInfo(Info);
}
/// On success returns the ID of the newly registered Reflection Info.
std::optional<uint32_t>
readPECOFF(RemoteAddress ImageStart,
llvm::SmallVector<llvm::StringRef, 1> PotentialModuleNames = {}) {
auto Buf = this->getReader().readBytes(ImageStart,
sizeof(llvm::object::dos_header));
if (!Buf)
return {};
auto DOSHdr = reinterpret_cast<const llvm::object::dos_header *>(Buf.get());
auto PEHeaderAddress = ImageStart + DOSHdr->AddressOfNewExeHeader;
Buf = this->getReader().readBytes(PEHeaderAddress,
sizeof(llvm::COFF::PEMagic));
if (!Buf)
return {};
if (memcmp(Buf.get(), llvm::COFF::PEMagic, sizeof(llvm::COFF::PEMagic)))
return {};
return readPECOFFSections(ImageStart, PotentialModuleNames);
}
/// On success returns the ID of the newly registered Reflection Info.
template <typename T>
std::optional<uint32_t> readELFSections(
RemoteAddress ImageStart,
std::optional<llvm::sys::MemoryBlock> FileBuffer,
llvm::SmallVector<llvm::StringRef, 1> PotentialModuleNames = {}) {
// When reading from the FileBuffer we can simply return a pointer to
// the underlying data.
// When reading from the process, we need to keep the memory around
// until the end of the function, so we store it inside ReadDataBuffer.
// We do this so in both cases we can return a simple pointer.
std::vector<MemoryReader::ReadBytesResult> ReadDataBuffer;
auto readData = [&](uint64_t Offset, uint64_t Size) -> const void * {
if (FileBuffer.has_value()) {
auto Buffer = FileBuffer.value();
if (Offset + Size > Buffer.allocatedSize())
return nullptr;
return (const void *)((uint64_t)Buffer.base() + Offset);
} else {
MemoryReader::ReadBytesResult Buf =
this->getReader().readBytes(ImageStart + Offset, Size);
if (!Buf)
return nullptr;
ReadDataBuffer.push_back(std::move(Buf));
return ReadDataBuffer.back().get();
}
};
const void *Buf = readData(0, sizeof(typename T::Header));
if (!Buf)
return {};
auto Hdr = reinterpret_cast<const typename T::Header *>(Buf);
assert(Hdr->getFileClass() == T::ELFClass && "invalid ELF file class");
// From the header, grab information about the section header table.
uint64_t SectionHdrAddress = Hdr->e_shoff;
uint16_t SectionHdrNumEntries = Hdr->e_shnum;
uint16_t SectionEntrySize = Hdr->e_shentsize;
if (sizeof(typename T::Section) > SectionEntrySize)
return {};
if (SectionHdrNumEntries == 0)
return {};
// Collect all the section headers, we need them to look up the
// reflection sections (by name) and the string table.
// We read the section headers from the FileBuffer, since they are
// not mapped in the child process.
std::vector<const typename T::Section *> SecHdrVec;
for (unsigned I = 0; I < SectionHdrNumEntries; ++I) {
uint64_t Offset = SectionHdrAddress + (I * SectionEntrySize);
auto SecBuf = readData(Offset, sizeof(typename T::Section));
if (!SecBuf)
return {};
const typename T::Section *SecHdr =
reinterpret_cast<const typename T::Section *>(SecBuf);
SecHdrVec.push_back(SecHdr);
}
// This provides quick access to the section header string table index.
// We also here handle the unlikely even where the section index overflows
// and it's just a pointer to secondary storage (SHN_XINDEX).
uint32_t SecIdx = Hdr->e_shstrndx;
if (SecIdx == llvm::ELF::SHN_XINDEX) {
assert(!SecHdrVec.empty() && "malformed ELF object");
SecIdx = SecHdrVec[0]->sh_link;
}
assert(SecIdx < SecHdrVec.size() && "malformed ELF object");
const typename T::Section *SecHdrStrTab = SecHdrVec[SecIdx];
typename T::Offset StrTabOffset = SecHdrStrTab->sh_offset;
typename T::Size StrTabSize = SecHdrStrTab->sh_size;
auto StrTabBuf = readData(StrTabOffset, StrTabSize);
if (!StrTabBuf)
return {};
auto StrTab = reinterpret_cast<const char *>(StrTabBuf);
bool Error = false;
// GNU ld and lld both merge sections regardless of the
// `SHF_GNU_RETAIN` flag. gold, presently, does not. The Swift
// compiler has a couple of switches that control whether or not
// the reflection sections are stripped; when these are enabled,
// it will _not_ set `SHF_GNU_RETAIN` on the reflection metadata
// sections. However, `swiftrt.o` contains declarations of the
// sections _with_ the `SHF_GNU_RETAIN` flag set, which makes
// sense since at runtime we will only expect to be able to access
// reflection metadata that we said we wanted to exist at runtime.
//
// The upshot is that when linking with gold, we can end up with
// two sets of reflection metadata sections. In a normal build
// where the compiler flags are the same for every linked object,
// we'll have *either* all retained *or* all un-retained sections
// (the retained sections will still exist because of `swiftrt.o`,
// but will be empty). The only time we'd expect to have a mix is
// where some code was compiled with a different setting of the
// metadata stripping flags. If that happens, the code below will
// simply add both sets of reflection sections, with the retained
// ones added first.
//
// See also https://sourceware.org/bugzilla/show_bug.cgi?id=31415.
auto findELFSectionByName =
[&](llvm::StringRef Name, bool Retained) -> std::pair<RemoteRef<void>, uint64_t> {
if (Error)
return {nullptr, 0};
// Now for all the sections, find their name.
for (const typename T::Section *Hdr : SecHdrVec) {
uint32_t Offset = Hdr->sh_name;
const char *Start = (const char *)StrTab + Offset;
uint64_t StringSize = strnlen(Start, StrTabSize - Offset);
if (StringSize > StrTabSize - Offset) {
Error = true;
break;
}
std::string SecName(Start, StringSize);
if (SecName != Name)
continue;
if (Retained != bool(Hdr->sh_flags & llvm::ELF::SHF_GNU_RETAIN))
continue;
RemoteAddress SecStart = ImageStart + Hdr->sh_addr;
auto SecSize = Hdr->sh_size;
MemoryReader::ReadBytesResult SecBuf;
if (FileBuffer.has_value()) {
// sh_offset gives us the offset to the section in the file,
// while sh_addr gives us the offset in the process.
auto Offset = Hdr->sh_offset;
if (FileBuffer->allocatedSize() < Offset + SecSize) {
Error = true;
break;
}
auto *Buf = malloc(SecSize);
SecBuf = MemoryReader::ReadBytesResult(
Buf, [](const void *ptr) { free(const_cast<void *>(ptr)); });
memcpy((void *)Buf,
(const void *)((uint64_t)FileBuffer->base() + Offset),
SecSize);
} else {
SecBuf = this->getReader().readBytes(SecStart, SecSize);
}
if (!SecBuf)
return {nullptr, 0};
auto SecContents = RemoteRef<void>(SecStart, SecBuf.get());
savedBuffers.push_back(std::move(SecBuf));
return {SecContents, SecSize};
}
return {nullptr, 0};
};
SwiftObjectFileFormatELF ObjectFileFormat;
auto FieldMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::fieldmd), true);
auto AssocTySec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::assocty), true);
auto BuiltinTySec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::builtin), true);
auto CaptureSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::capture), true);
auto TypeRefMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::typeref), true);
auto ReflStrMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::reflstr), true);
auto ConformMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::conform), true);
auto MPEnumMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::mpenum), true);
if (Error)
return {};
std::optional<uint32_t> result = {};
// We succeed if at least one of the sections is present in the
// ELF executable.
if (FieldMdSec.first || AssocTySec.first || BuiltinTySec.first ||
CaptureSec.first || TypeRefMdSec.first || ReflStrMdSec.first ||
ConformMdSec.first || MPEnumMdSec.first) {
ReflectionInfo info = {{FieldMdSec.first, FieldMdSec.second},
{AssocTySec.first, AssocTySec.second},
{BuiltinTySec.first, BuiltinTySec.second},
{CaptureSec.first, CaptureSec.second},
{TypeRefMdSec.first, TypeRefMdSec.second},
{ReflStrMdSec.first, ReflStrMdSec.second},
{ConformMdSec.first, ConformMdSec.second},
{MPEnumMdSec.first, MPEnumMdSec.second},
PotentialModuleNames};
result = this->addReflectionInfo(info);
}
// Also check for the non-retained versions of the sections; we'll
// only return a single reflection info ID if both are found (and it'll
// be the one for the retained sections if we have them), but we'll
// still add all the reflection information.
FieldMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::fieldmd), false);
AssocTySec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::assocty), false);
BuiltinTySec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::builtin), false);
CaptureSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::capture), false);
TypeRefMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::typeref), false);
ReflStrMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::reflstr), false);
ConformMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::conform), false);
MPEnumMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::mpenum), false);
if (Error)
return {};
if (FieldMdSec.first || AssocTySec.first || BuiltinTySec.first ||
CaptureSec.first || TypeRefMdSec.first || ReflStrMdSec.first ||
ConformMdSec.first || MPEnumMdSec.first) {
ReflectionInfo info = {{FieldMdSec.first, FieldMdSec.second},
{AssocTySec.first, AssocTySec.second},
{BuiltinTySec.first, BuiltinTySec.second},
{CaptureSec.first, CaptureSec.second},
{TypeRefMdSec.first, TypeRefMdSec.second},
{ReflStrMdSec.first, ReflStrMdSec.second},
{ConformMdSec.first, ConformMdSec.second},
{MPEnumMdSec.first, MPEnumMdSec.second},
PotentialModuleNames};
auto rid = this->addReflectionInfo(info);
if (!result)
result = rid;
}
return result;
}
/// Parses metadata information from an ELF image. Because the Section
/// Header Table maybe be missing (for example, when reading from a
/// process) this method optionally receives a buffer with the contents
/// of the image's file, from where it will the necessary information.
///
///
/// \param[in] ImageStart
/// A remote address pointing to the start of the image in the running
/// process.
///
/// \param[in] FileBuffer
/// A buffer which contains the contents of the image's file
/// in disk. If missing, all the information will be read using the
/// instance's memory reader.
///
/// \return
/// \b The newly added reflection info ID if successful,
/// \b std::nullopt otherwise.
std::optional<uint32_t>
readELF(RemoteAddress ImageStart,
std::optional<llvm::sys::MemoryBlock> FileBuffer,
llvm::SmallVector<llvm::StringRef, 1> PotentialModuleNames = {}) {
auto Buf =
this->getReader().readBytes(ImageStart, sizeof(llvm::ELF::Elf64_Ehdr));
if (!Buf)
return std::nullopt;
// Read the header.
auto Hdr = reinterpret_cast<const llvm::ELF::Elf64_Ehdr *>(Buf.get());
if (!Hdr->checkMagic())
return std::nullopt;
// Check if we have a ELFCLASS32 or ELFCLASS64
unsigned char FileClass = Hdr->getFileClass();
if (FileClass == llvm::ELF::ELFCLASS64) {
return readELFSections<ELFTraits<llvm::ELF::ELFCLASS64>>(
ImageStart, FileBuffer, PotentialModuleNames);
} else if (FileClass == llvm::ELF::ELFCLASS32) {
return readELFSections<ELFTraits<llvm::ELF::ELFCLASS32>>(
ImageStart, FileBuffer, PotentialModuleNames);
} else {
return std::nullopt;
}
}
/// On success returns the ID of the newly registered Reflection Info.
std::optional<uint32_t>
addImage(RemoteAddress ImageStart,
llvm::SmallVector<llvm::StringRef, 1> PotentialModuleNames = {}) {
// Read the first few bytes to look for a magic header.
auto Magic = this->getReader().readBytes(ImageStart, sizeof(uint32_t));
if (!Magic)
return {};
uint32_t MagicWord;
memcpy(&MagicWord, Magic.get(), sizeof(MagicWord));
// 32- and 64-bit Mach-O.
if (MagicWord == llvm::MachO::MH_MAGIC) {
return readMachOSections<MachOTraits<4>>(ImageStart, PotentialModuleNames);
}
if (MagicWord == llvm::MachO::MH_MAGIC_64) {
return readMachOSections<MachOTraits<8>>(ImageStart, PotentialModuleNames);
}
// PE. (This just checks for the DOS header; `readPECOFF` will further
// validate the existence of the PE header.)
auto MagicBytes = (const char*)Magic.get();
if (MagicBytes[0] == 'M' && MagicBytes[1] == 'Z') {
return readPECOFF(ImageStart, PotentialModuleNames);
}
// ELF.
if (MagicBytes[0] == llvm::ELF::ElfMagic[0]
&& MagicBytes[1] == llvm::ELF::ElfMagic[1]
&& MagicBytes[2] == llvm::ELF::ElfMagic[2]
&& MagicBytes[3] == llvm::ELF::ElfMagic[3]) {
return readELF(ImageStart, std::optional<llvm::sys::MemoryBlock>(),
PotentialModuleNames);
}
// We don't recognize the format.
return std::nullopt;
}
/// Adds an image using the FindSection closure to find the swift metadata
/// sections. \param FindSection
/// Closure that finds sections by name. ReflectionContext is in charge
/// of freeing the memory buffer in the RemoteRef return value.
/// process.
/// \return
/// \b The newly added reflection info ID if successful,
/// \b std::nullopt otherwise.
std::optional<uint32_t>
addImage(llvm::function_ref<
std::pair<RemoteRef<void>, uint64_t>(ReflectionSectionKind)>
FindSection,
llvm::SmallVector<llvm::StringRef, 1> PotentialModuleNames = {}) {
auto Sections = {
ReflectionSectionKind::fieldmd, ReflectionSectionKind::assocty,
ReflectionSectionKind::builtin, ReflectionSectionKind::capture,
ReflectionSectionKind::typeref, ReflectionSectionKind::reflstr,
ReflectionSectionKind::conform, ReflectionSectionKind::mpenum};
llvm::SmallVector<std::pair<RemoteRef<void>, uint64_t>, 6> Pairs;
for (auto Section : Sections) {
Pairs.push_back(FindSection(Section));
auto LatestRemoteRef = std::get<RemoteRef<void>>(Pairs.back());
if (LatestRemoteRef) {
MemoryReader::ReadBytesResult Buffer(
LatestRemoteRef.getLocalBuffer(),
[](const void *Ptr) { free(const_cast<void *>(Ptr)); });
savedBuffers.push_back(std::move(Buffer));
}
}
// If we didn't find any sections, return.
if (llvm::all_of(Pairs, [](const auto &Pair) { return !Pair.first; }))
return {};
ReflectionInfo Info = {{Pairs[0].first, Pairs[0].second},
{Pairs[1].first, Pairs[1].second},
{Pairs[2].first, Pairs[2].second},
{Pairs[3].first, Pairs[3].second},
{Pairs[4].first, Pairs[4].second},
{Pairs[5].first, Pairs[5].second},
{Pairs[6].first, Pairs[6].second},
{Pairs[7].first, Pairs[7].second},
PotentialModuleNames};
return addReflectionInfo(Info);
}
/// Adds the reflection info and returns it's id.
uint32_t addReflectionInfo(ReflectionInfo I) {
return getBuilder().addReflectionInfo(I);
}
bool ownsObject(RemoteAddress ObjectAddress) {
auto MetadataAddress = readMetadataFromInstance(ObjectAddress);
if (!MetadataAddress)
return true;
return ownsAddress(*MetadataAddress);
}
/// Returns true if the address falls within the given address ranges.
bool ownsAddress(
RemoteAddress Address,
const std::vector<std::tuple<RemoteAddress, RemoteAddress>> &ranges) {
for (auto Range : ranges) {
auto Start = std::get<0>(Range);
auto End = std::get<1>(Range);
if (Start <= Address && Address < End)
return true;
}
return false;
}
/// Returns true if the address is known to the reflection context.
/// Currently, that means that either the address falls within the text or
/// data segments of a registered image, or optionally, the address points
/// to a Metadata whose type context descriptor is within the text segment
/// of a registered image.
bool ownsAddress(RemoteAddress Address, bool checkMetadataDescriptor = true) {
if (ownsAddress(Address, textRanges))
return true;
if (ownsAddress(Address, dataRanges))
return true;
if (checkMetadataDescriptor) {
// This is usually called on a Metadata address which might have been
// on the heap. Try reading it and looking up its type context descriptor
// instead.
if (auto Metadata = readMetadata(Address))
if (auto DescriptorAddress =
super::readAddressOfNominalTypeDescriptor(Metadata, true))
if (ownsAddress(DescriptorAddress, textRanges))
return true;
}
return false;
}
/// Returns the address of the nominal type descriptor given a metadata
/// address.
RemoteAddress
nominalTypeDescriptorFromMetadata(RemoteAddress MetadataAddress) {
auto Metadata = readMetadata(MetadataAddress);
if (!Metadata)
return RemoteAddress();
return super::readAddressOfNominalTypeDescriptor(Metadata, true);
}
/// Return a description of the layout of a class instance with the given
/// metadata as its isa pointer.
const RecordTypeInfo *
getMetadataTypeInfo(RemoteAddress MetadataAddress,
remote::TypeInfoProvider *ExternalTypeInfo) {
// See if we cached the layout already
auto ExternalTypeInfoId = ExternalTypeInfo ? ExternalTypeInfo->getId() : 0;
auto found = Cache.find({MetadataAddress, ExternalTypeInfoId});
if (found != Cache.end())
return found->second;
auto &TC = getBuilder().getTypeConverter();
const RecordTypeInfo *TI = nullptr;
auto TR = readTypeFromMetadata(MetadataAddress);
auto kind = this->readKindFromMetadata(MetadataAddress);
if (TR != nullptr && kind) {
switch (*kind) {
case MetadataKind::Class: {
// Figure out where the stored properties of this class begin
// by looking at the size of the superclass
auto start =
this->readInstanceStartFromClassMetadata(MetadataAddress);
// Perform layout
if (start)
TI = TC.getClassInstanceTypeInfo(TR, *start, ExternalTypeInfo);
break;
}
default:
break;
}
}
// Cache the result for future lookups
Cache[{MetadataAddress, ExternalTypeInfoId}] = TI;
return TI;
}
/// Return a description of the layout of a class instance with the given
/// metadata as its isa pointer.
const TypeInfo *
getInstanceTypeInfo(RemoteAddress ObjectAddress,
remote::TypeInfoProvider *ExternalTypeInfo) {
auto MetadataAddress = readMetadataFromInstance(ObjectAddress);
if (!MetadataAddress)
return nullptr;
auto kind = this->readKindFromMetadata(*MetadataAddress);
if (!kind)
return nullptr;
switch (*kind) {
case MetadataKind::Class:
return getMetadataTypeInfo(*MetadataAddress, ExternalTypeInfo);
case MetadataKind::HeapLocalVariable: {
auto CDAddr = this->readCaptureDescriptorFromMetadata(*MetadataAddress);
if (!CDAddr)
return nullptr;
if (!CDAddr->getResolvedAddress())
return nullptr;
// FIXME: Non-generic SIL boxes also use the HeapLocalVariable metadata
// kind, but with a null capture descriptor right now (see
// FixedBoxTypeInfoBase::allocate).
//
// Non-generic SIL boxes share metadata among types with compatible
// layout, but we need some way to get an outgoing pointer map for them.
auto CD = getBuilder().getCaptureDescriptor(CDAddr->getResolvedAddress());
if (CD == nullptr)
return nullptr;
auto Info = getBuilder().getClosureContextInfo(CD);
return getClosureContextInfo(ObjectAddress, Info, ExternalTypeInfo);
}
case MetadataKind::HeapGenericLocalVariable: {
// Generic SIL @box type - there is always an instantiated metadata
// pointer for the boxed type.
if (auto Meta = readMetadata(*MetadataAddress)) {
if (auto *GenericHeapMeta = cast<TargetGenericBoxHeapMetadata<Runtime>>(
Meta.getLocalBuffer())) {
auto BoxedTypeAddress = RemoteAddress(
GenericHeapMeta->BoxedType, ObjectAddress.getAddressSpace());
auto TR = readTypeFromMetadata(BoxedTypeAddress);
return getTypeInfo(TR, ExternalTypeInfo);
}
}
return nullptr;
}
case MetadataKind::ErrorObject:
// Error boxed existential on non-Objective-C runtime target
return nullptr;
default:
return nullptr;
}
}
std::optional<std::pair<const TypeRef *, RemoteAddress>>
getDynamicTypeAndAddressClassExistential(RemoteAddress ExistentialAddress) {
auto PointerValue = readResolvedPointerValue(ExistentialAddress);
if (!PointerValue)
return {};
auto Result = readMetadataFromInstance(*PointerValue);
if (!Result)
return {};
auto TypeResult = readTypeFromMetadata(Result.value());
if (!TypeResult)
return {};
return {{std::move(TypeResult), *PointerValue}};
}
std::optional<std::pair<const TypeRef *, RemoteAddress>>
getDynamicTypeAndAddressErrorExistential(RemoteAddress ExistentialAddress,
bool *IsBridgedError = nullptr) {
auto Result = readMetadataAndValueErrorExistential(ExistentialAddress);
if (!Result)
return {};
auto TypeResult = readTypeFromMetadata(Result->MetadataAddress);
if (!TypeResult)
return {};
if (IsBridgedError)
*IsBridgedError = Result->IsBridgedError;
return {{TypeResult, Result->PayloadAddress}};
}
std::optional<std::pair<const TypeRef *, RemoteAddress>>
getDynamicTypeAndAddressOpaqueExistential(RemoteAddress ExistentialAddress) {
auto Result = readMetadataAndValueOpaqueExistential(ExistentialAddress);
if (!Result)
return {};
auto TypeResult = readTypeFromMetadata(Result->MetadataAddress);
if (!TypeResult)
return {};
return {{std::move(TypeResult), Result->PayloadAddress}};
}
bool projectExistential(RemoteAddress ExistentialAddress,
const TypeRef *ExistentialTR,
const TypeRef **OutInstanceTR,
RemoteAddress *OutInstanceAddress,
remote::TypeInfoProvider *ExternalTypeInfo) {
if (ExistentialTR == nullptr)
return false;
auto ExistentialTI = getTypeInfo(ExistentialTR, ExternalTypeInfo);
if (ExistentialTI == nullptr)
return false;
auto ExistentialRecordTI = dyn_cast<const RecordTypeInfo>(ExistentialTI);
if (ExistentialRecordTI == nullptr)
return false;
switch (ExistentialRecordTI->getRecordKind()) {
// Class existentials have trivial layout.
// It is itself the pointer to the instance followed by the witness tables.
case RecordKind::ClassExistential:
// This is just AnyObject.
*OutInstanceTR = ExistentialRecordTI->getFields()[0].TR;
*OutInstanceAddress = ExistentialAddress;
return true;
case RecordKind::OpaqueExistential: {
auto OptMetaAndValue =
readMetadataAndValueOpaqueExistential(ExistentialAddress);
if (!OptMetaAndValue)
return false;
auto InstanceTR = readTypeFromMetadata(OptMetaAndValue->MetadataAddress);
if (!InstanceTR)
return false;
*OutInstanceTR = InstanceTR;
*OutInstanceAddress = OptMetaAndValue->PayloadAddress;
return true;
}
case RecordKind::ErrorExistential: {
auto OptMetaAndValue =
readMetadataAndValueErrorExistential(ExistentialAddress);
if (!OptMetaAndValue)
return false;
// FIXME: Check third value, 'IsBridgedError'
auto InstanceTR = readTypeFromMetadata(OptMetaAndValue->MetadataAddress);
if (!InstanceTR)
return false;
*OutInstanceTR = InstanceTR;
*OutInstanceAddress = OptMetaAndValue->PayloadAddress;
return true;
}
default:
return false;
}
}
/// A version of `projectExistential` tailored for LLDB.
/// This version dereferences the resulting TypeRef if it wraps
/// a class type, it also dereferences the input `ExistentialAddress` before
/// attempting to find its dynamic type and address when dealing with error
/// existentials.
std::optional<std::pair<const TypeRef *, RemoteAddress>>
projectExistentialAndUnwrapClass(RemoteAddress ExistentialAddress,
const TypeRef &ExistentialTR) {
auto IsClass = [](const TypeRef *TypeResult) {
// When the existential wraps a class type, LLDB expects that the
// address returned is the class instance itself and not the address
// of the reference.
bool IsClass = TypeResult->getKind() == TypeRefKind::ForeignClass ||
TypeResult->getKind() == TypeRefKind::ObjCClass;
if (auto *nominal = llvm::dyn_cast<NominalTypeRef>(TypeResult))
IsClass = nominal->isClass();
else if (auto *boundGeneric =
llvm::dyn_cast<BoundGenericTypeRef>(TypeResult))
IsClass = boundGeneric->isClass();
return IsClass;
};
auto DereferenceAndSet = [&](RemoteAddress &Address) {
auto PointerValue = readResolvedPointerValue(Address);
if (!PointerValue)
return false;
Address = *PointerValue;
return true;
};
auto ExistentialRecordTI = getRecordTypeInfo(&ExistentialTR, nullptr);
if (!ExistentialRecordTI)
return {};
switch (ExistentialRecordTI->getRecordKind()) {
case RecordKind::ClassExistential:
return getDynamicTypeAndAddressClassExistential(ExistentialAddress);
case RecordKind::ErrorExistential: {
// LLDB stores the address of the error pointer.
if (!DereferenceAndSet(ExistentialAddress))
return {};
bool IsBridgedError = false;
auto Pair = getDynamicTypeAndAddressErrorExistential(ExistentialAddress,
&IsBridgedError);
if (!Pair)
return {};
if (!IsBridgedError && IsClass(std::get<const TypeRef *>(*Pair)))
if (!DereferenceAndSet(std::get<RemoteAddress>(*Pair)))
return {};
return Pair;
}
case RecordKind::OpaqueExistential: {
auto Pair = getDynamicTypeAndAddressOpaqueExistential(ExistentialAddress);
if (!Pair)
return {};
if (IsClass(std::get<const TypeRef *>(*Pair)))
if (!DereferenceAndSet(std::get<RemoteAddress>(*Pair)))
return {};
return Pair;
}
default:
return {};
}
}
/// Projects the value of an enum.
///
/// Takes the address and typeref for an enum and determines the
/// index of the currently-selected case within the enum.
/// You can use this index with `swift_reflection_childOfTypeRef`
/// to get detailed information about the specific case.
///
/// Returns true if the enum case could be successfully determined. In
/// particular, note that this code may return false for valid in-memory data
/// if the compiler used a strategy we do not yet understand.
bool projectEnumValue(RemoteAddress EnumAddress, const TypeRef *EnumTR,
int *CaseIndex,
remote::TypeInfoProvider *ExternalTypeInfo) {
// Get the TypeInfo and soundness-check it
if (EnumTR == nullptr) {
return false;
}
auto TI = getTypeInfo(EnumTR, ExternalTypeInfo);
if (TI == nullptr) {
return false;
}
auto EnumTI = dyn_cast<const EnumTypeInfo>(TI);
if (EnumTI == nullptr){
return false;
}
return EnumTI->projectEnumValue(getReader(), EnumAddress, CaseIndex);
}
/// Return a description of the layout of a value with the given type.
const TypeInfo *getTypeInfo(const TypeRef *TR,
remote::TypeInfoProvider *ExternalTypeInfo) {
if (TR == nullptr) {
return nullptr;
} else {
return getBuilder().getTypeConverter().getTypeInfo(TR, ExternalTypeInfo);
}
}
llvm::Expected<const TypeInfo &>
getTypeInfo(const TypeRef &TR, remote::TypeInfoProvider *ExternalTypeInfo) {
auto &TC = getBuilder().getTypeConverter();
const TypeInfo *TI = TC.getTypeInfo(&TR, ExternalTypeInfo);
if (!TI)
return llvm::createStringError(TC.takeLastError());
return *TI;
}
/// Given a typeref, attempt to calculate the unaligned start of this
/// instance's fields. For example, for a type without a superclass, the start
/// of the instance fields would after the word for the isa pointer and the
/// word for the refcount field. For a subclass the start would be the after
/// the superclass's fields. For a version of this function that performs the
/// same job but starting out with an instance pointer check
/// MetadataReader::readInstanceStartFromClassMetadata.
std::optional<unsigned>
computeUnalignedFieldStartOffset(const TypeRef *TR,
remote::TypeInfoProvider *ExternalTypeInfo) {
size_t isaAndRetainCountSize = sizeof(StoredSize) + sizeof(long long);
const TypeRef *superclass = getBuilder().lookupSuperclass(TR);
if (!superclass)
// If there is no superclass the stat of the instance's field is right
// after the isa and retain fields.
return isaAndRetainCountSize;
// `ObjCClassTypeRef` instances represent classes in the ObjC module ("__C").
// These will never have Swift type metadata.
if (auto *objcSuper = dyn_cast<ObjCClassTypeRef>(superclass))
if (auto *superTI = ExternalTypeInfo->getTypeInfo(objcSuper->getName()))
return superTI->getSize();
auto superclassStart =
computeUnalignedFieldStartOffset(superclass, ExternalTypeInfo);
if (!superclassStart)
return std::nullopt;
auto *superTI = getBuilder().getTypeConverter().getClassInstanceTypeInfo(
superclass, *superclassStart, ExternalTypeInfo);
if (!superTI)
return std::nullopt;
// The start of the subclass's fields is right after the super class's ones.
size_t start = superTI->getSize();
return start;
}
const RecordTypeInfo *
getRecordTypeInfo(const TypeRef *TR,
remote::TypeInfoProvider *ExternalTypeInfo) {
auto *TypeInfo = getTypeInfo(TR, ExternalTypeInfo);
return dyn_cast_or_null<const RecordTypeInfo>(TypeInfo);
}
bool metadataIsActor(RemoteAddress MetadataAddress) {
auto Metadata = readMetadata(MetadataAddress);
if (!Metadata)
return false;
// Only classes can be actors.
if (Metadata->getKind() != MetadataKind::Class)
return false;
auto DescriptorAddress =
super::readAddressOfNominalTypeDescriptor(Metadata);
if (!DescriptorAddress)
return false;
if (!ownsAddress(DescriptorAddress, textRanges))
return false;
auto DescriptorBytes = getReader().readBytes(
DescriptorAddress, sizeof(TargetTypeContextDescriptor<Runtime>));
if (!DescriptorBytes)
return false;
auto Descriptor =
reinterpret_cast<const TargetTypeContextDescriptor<Runtime> *>(
DescriptorBytes.get());
return Descriptor->getTypeContextDescriptorFlags().class_isActor();
}
/// Iterate the protocol conformance cache tree rooted at NodePtr, calling
/// Call with the type and protocol in each node.
void iterateConformanceTree(
RemoteAddress NodePtr,
std::function<void(RemoteAddress Type, RemoteAddress Proto)> Call) {
if (!NodePtr)
return;
auto NodeBytes =
getReader().readBytes(NodePtr, sizeof(ConformanceNode<Runtime>));
if (!NodeBytes)
return;
auto NodeData =
reinterpret_cast<const ConformanceNode<Runtime> *>(NodeBytes.get());
Call(NodeData->Type, NodeData->Proto);
iterateConformanceTree(NodeData->Left, Call);
iterateConformanceTree(NodeData->Right, Call);
}
void IterateConformanceTable(
RemoteAddress ConformancesPtr,
std::function<void(RemoteAddress Type, RemoteAddress Proto)> Call) {
auto MapBytes = getReader().readBytes(ConformancesPtr,
sizeof(ConcurrentHashMap<Runtime>));
if (!MapBytes)
return;
auto MapData =
reinterpret_cast<const ConcurrentHashMap<Runtime> *>(MapBytes.get());
auto Count = MapData->ElementCount;
auto Size = Count * sizeof(ConformanceCacheEntry<Runtime>) + sizeof(StoredPointer);
auto ElementsBytes = getReader().readBytes(
RemoteAddress(MapData->Elements, ConformancesPtr.getAddressSpace()),
Size);
if (!ElementsBytes)
return;
auto ElementsData =
reinterpret_cast<const ConformanceCacheEntry<Runtime> *>(
reinterpret_cast<const char *>(ElementsBytes.get()) + sizeof(StoredPointer));
for (StoredSize i = 0; i < Count; i++) {
auto &Element = ElementsData[i];
Call(RemoteAddress(Element.Type, ConformancesPtr.getAddressSpace()),
RemoteAddress(Element.Proto, ConformancesPtr.getAddressSpace()));
}
}
/// Iterate the protocol conformance cache in the target process, calling Call
/// with the type and protocol of each conformance. Returns None on success,
/// and a string describing the error on failure.
std::optional<std::string> iterateConformances(
std::function<void(RemoteAddress Type, RemoteAddress Proto)> Call) {
std::string ConformancesPointerName =
"_swift_debug_protocolConformanceStatePointer";
auto ConformancesAddrAddr =
getReader().getSymbolAddress(ConformancesPointerName);
if (!ConformancesAddrAddr)
return "unable to look up debug variable " + ConformancesPointerName;
auto ConformancesAddr =
getReader().readPointer(ConformancesAddrAddr, sizeof(StoredPointer));
if (!ConformancesAddr)
return "unable to read value of " + ConformancesPointerName;
IterateConformanceTable(ConformancesAddr->getResolvedAddress(), Call);
return std::nullopt;
}
/// Fetch the metadata pointer from a metadata allocation, or 0 if this
/// allocation's tag is not handled or an error occurred.
StoredPointer allocationMetadataPointer(
MetadataAllocation<Runtime> Allocation) {
if (Allocation.Tag == GenericMetadataCacheTag) {
auto AllocationBytes = getReader().readBytes(
RemoteAddress(Allocation.Ptr, RemoteAddress::DefaultAddressSpace),
Allocation.Size);
if (!AllocationBytes)
return 0;
auto Entry =
reinterpret_cast<const GenericMetadataCacheEntry<StoredPointer> *>(
AllocationBytes.get());
return Entry->Value;
}
return 0;
}
/// Get the name of a metadata tag, if known.
std::optional<std::string> metadataAllocationTagName(int Tag) {
switch (Tag) {
#define TAG(name, value) \
case value: \
return std::string(#name);
#include "../../../stdlib/public/runtime/MetadataAllocatorTags.def"
default:
return std::nullopt;
}
}
std::optional<MetadataCacheNode<Runtime>>
metadataAllocationCacheNode(MetadataAllocation<Runtime> Allocation) {
switch (Allocation.Tag) {
case BoxesTag:
case ObjCClassWrappersTag:
case FunctionTypesTag:
case MetatypeTypesTag:
case ExistentialMetatypeValueWitnessTablesTag:
case ExistentialMetatypesTag:
case ExistentialTypesTag:
case ExtendedExistentialTypesTag:
case ExtendedExistentialTypeShapesTag:
case OpaqueExistentialValueWitnessTablesTag:
case ClassExistentialValueWitnessTablesTag:
case ForeignWitnessTablesTag:
case TupleCacheTag:
case GenericMetadataCacheTag:
case ForeignMetadataCacheTag:
case GenericWitnessTableCacheTag: {
auto NodeBytes = getReader().readBytes(
RemoteAddress(Allocation.Ptr, RemoteAddress::DefaultAddressSpace),
sizeof(MetadataCacheNode<Runtime>));
if (!NodeBytes)
return std::nullopt;
auto Node =
reinterpret_cast<const MetadataCacheNode<Runtime> *>(NodeBytes.get());
return *Node;
}
default:
return std::nullopt;
}
}
/// Iterate the metadata allocations in the target process, calling Call with
/// each allocation found. Returns None on success, and a string describing
/// the error on failure.
std::optional<std::string> iterateMetadataAllocations(
std::function<void(MetadataAllocation<Runtime>)> Call) {
std::string IterationEnabledName =
"_swift_debug_metadataAllocationIterationEnabled";
std::string AllocationPoolPointerName =
"_swift_debug_allocationPoolPointer";
auto IterationEnabledAddr =
getReader().getSymbolAddress(IterationEnabledName);
if (!IterationEnabledAddr)
return "unable to look up debug variable " + IterationEnabledName;
char IterationEnabled;
if (!getReader().readInteger(IterationEnabledAddr, &IterationEnabled))
return "failed to read value of " + IterationEnabledName;
if (!IterationEnabled)
return std::string("remote process does not have metadata allocation "
"iteration enabled");
auto AllocationPoolAddrAddr =
getReader().getSymbolAddress(AllocationPoolPointerName);
if (!AllocationPoolAddrAddr)
return "unable to look up debug variable " + AllocationPoolPointerName;
auto AllocationPoolAddr =
getReader().readPointer(AllocationPoolAddrAddr, sizeof(StoredPointer));
if (!AllocationPoolAddr)
return "failed to read value of " + AllocationPoolPointerName;
struct PoolRange {
RemoteAddress Begin;
StoredSize Remaining;
};
struct PoolTrailer {
RemoteAddress PrevTrailer;
StoredSize PoolSize;
};
struct alignas(RemoteAddress) AllocationHeader {
uint16_t Size;
uint16_t Tag;
};
auto PoolBytes = getReader()
.readBytes(AllocationPoolAddr->getResolvedAddress(), sizeof(PoolRange));
if (!PoolBytes)
return std::string("failure reading allocation pool contents");
auto Pool = reinterpret_cast<const PoolRange *>(PoolBytes.get());
// Limit how many iterations of this loop we'll do, to avoid potential
// infinite loops when reading bad data. Limit to 1 million iterations. In
// normal operation, each pool allocation is 16kB, so that would be ~16GB of
// metadata which is far more than any normal program should have.
unsigned LoopCount = 0;
unsigned LoopLimit = 1000000;
auto TrailerPtr = Pool->Begin + Pool->Remaining;
while (TrailerPtr && LoopCount++ < LoopLimit) {
auto TrailerBytes =
getReader().readBytes(TrailerPtr, sizeof(PoolTrailer));
if (!TrailerBytes)
break;
auto Trailer = reinterpret_cast<const PoolTrailer *>(TrailerBytes.get());
auto PoolStart = TrailerPtr - Trailer->PoolSize;
auto PoolBytes = getReader().readBytes(PoolStart, Trailer->PoolSize);
if (!PoolBytes)
break;
auto PoolPtr = (const char *)PoolBytes.get();
uintptr_t Offset = 0;
while (Offset < Trailer->PoolSize) {
auto AllocationPtr = PoolPtr + Offset;
auto Header = (const AllocationHeader *)AllocationPtr;
if (Header->Size == 0)
break;
auto RemoteAddr = PoolStart + Offset + sizeof(AllocationHeader);
MetadataAllocation<Runtime> Allocation;
Allocation.Tag = Header->Tag;
if (RemoteAddr.getAddressSpace() != RemoteAddress::DefaultAddressSpace)
return "storing remote address from non-default address space.";
Allocation.Ptr = RemoteAddr.getRawAddress();
Allocation.Size = Header->Size;
Call(Allocation);
Offset += sizeof(AllocationHeader) + Header->Size;
}
TrailerPtr = Trailer->PrevTrailer;
}
return std::nullopt;
}
std::optional<std::string> iterateMetadataAllocationBacktraces(
std::function<void(StoredPointer, uint32_t, const StoredPointer *)>
Call) {
std::string BacktraceListName =
"_swift_debug_metadataAllocationBacktraceList";
auto BacktraceListAddr = getReader().getSymbolAddress(BacktraceListName);
if (!BacktraceListAddr)
return "unable to look up debug variable " + BacktraceListName;
auto BacktraceListNextPtr =
getReader().readPointer(BacktraceListAddr, sizeof(StoredPointer));
if (!BacktraceListNextPtr)
return std::nullopt;
// Limit how many iterations of this loop we'll do, to avoid potential
// infinite loops when reading bad data. Limit to 1 billion iterations. In
// normal operation, a program shouldn't have anywhere near 1 billion
// metadata allocations.
unsigned LoopCount = 0;
unsigned LoopLimit = 1000000000;
auto BacktraceListNext = BacktraceListNextPtr->getResolvedAddress();
while (BacktraceListNext && LoopCount++ < LoopLimit) {
auto HeaderBytes = getReader().readBytes(
BacktraceListNext,
sizeof(MetadataAllocationBacktraceHeader<Runtime>));
if (!HeaderBytes) {
// FIXME: std::stringstream would be better, but LLVM's standard library
// introduces a vtable and we don't want that.
char result[128];
std::snprintf(result, sizeof(result),
"unable to read Next pointer %#" PRIx64,
BacktraceListNext.getRawAddress());
return std::string(result);
}
auto HeaderPtr =
reinterpret_cast<const MetadataAllocationBacktraceHeader<Runtime> *>(
HeaderBytes.get());
auto BacktraceAddrPtr =
BacktraceListNext +
sizeof(MetadataAllocationBacktraceHeader<Runtime>);
auto BacktraceBytes = getReader().readBytes(
BacktraceAddrPtr, HeaderPtr->Count * sizeof(StoredPointer));
auto BacktracePtr =
reinterpret_cast<const StoredPointer *>(BacktraceBytes.get());
Call(HeaderPtr->Allocation, HeaderPtr->Count, BacktracePtr);
BacktraceListNext =
RemoteAddress(HeaderPtr->Next, RemoteAddress::DefaultAddressSpace);
}
return std::nullopt;
}
std::pair<std::optional<std::string>, AsyncTaskSlabInfo>
asyncTaskSlabAllocations(StoredPointer SlabPtr) {
using StackAllocator = StackAllocator<Runtime>;
auto SlabBytes = getReader().readBytes(
RemoteAddress(SlabPtr, RemoteAddress::DefaultAddressSpace),
sizeof(typename StackAllocator::Slab));
auto Slab = reinterpret_cast<const typename StackAllocator::Slab *>(
SlabBytes.get());
if (!Slab)
return {std::string("failure reading slab"), {}};
// For now, we won't try to walk the allocations in the slab, we'll just
// provide the whole thing as one big chunk.
size_t HeaderSize =
llvm::alignTo(sizeof(*Slab), llvm::Align(MaximumAlignment));
AsyncTaskAllocationChunk AllocatedSpaceChunk;
AllocatedSpaceChunk.Start = SlabPtr + HeaderSize;
AllocatedSpaceChunk.Length = Slab->CurrentOffset;
AllocatedSpaceChunk.Kind = AsyncTaskAllocationChunk::ChunkKind::Unknown;
// Provide a second chunk just for the Next pointer, so the client knows
// that there's an allocation there.
AsyncTaskAllocationChunk NextPtrChunk;
NextPtrChunk.Start =
SlabPtr + offsetof(typename StackAllocator::Slab, Next);
NextPtrChunk.Length = sizeof(Slab->Next);
NextPtrChunk.Kind = AsyncTaskAllocationChunk::ChunkKind::RawPointer;
// Total slab size is the slab's capacity plus the header.
auto SlabSize = Slab->Capacity + HeaderSize;
return {std::nullopt,
{Slab->Next, SlabSize, {NextPtrChunk, AllocatedSpaceChunk}}};
}
std::pair<std::optional<std::string>, AsyncTaskInfo>
asyncTaskInfo(RemoteAddress AsyncTaskPtr, unsigned ChildTaskLimit,
unsigned AsyncBacktraceLimit) {
loadTargetPointers();
if (supportsPriorityEscalation)
return asyncTaskInfo<
AsyncTask<Runtime, ActiveTaskStatusWithEscalation<Runtime>>>(
AsyncTaskPtr, ChildTaskLimit, AsyncBacktraceLimit);
else
return asyncTaskInfo<
AsyncTask<Runtime, ActiveTaskStatusWithoutEscalation<Runtime>>>(
AsyncTaskPtr, ChildTaskLimit, AsyncBacktraceLimit);
}
std::pair<std::optional<std::string>, ActorInfo>
actorInfo(RemoteAddress ActorPtr) {
if (supportsPriorityEscalation)
return actorInfo<
DefaultActorImpl<Runtime, ActiveActorStatusWithEscalation<Runtime>>>(
ActorPtr);
else
return actorInfo<DefaultActorImpl<
Runtime, ActiveActorStatusWithoutEscalation<Runtime>>>(ActorPtr);
}
StoredPointer nextJob(RemoteAddress JobPtr) {
using Job = Job<Runtime>;
auto JobBytes = getReader().readBytes(JobPtr, sizeof(Job));
auto *JobObj = reinterpret_cast<const Job *>(JobBytes.get());
if (!JobObj)
return 0;
// This is a JobRef which stores flags in the low bits.
return JobObj->SchedulerPrivate[0] & ~StoredPointer(0x3);
}
private:
void setIsRunning(
AsyncTaskInfo &Info,
const AsyncTask<Runtime, ActiveTaskStatusWithEscalation<Runtime>> *Task) {
#if HAS_DISPATCH_LOCK_IS_LOCKED
Info.HasIsRunning = true;
Info.IsRunning =
dispatch_lock_is_locked(Task->PrivateStorage.Status.ExecutionLock[0]);
#else
// The target runtime was built with priority escalation but we don't have
// the swift_concurrency_private.h header needed to decode the running
// status in the task. Set HasIsRunning to false to indicate that we can't
// tell whether or not the task is running.
Info.HasIsRunning = false;
#endif
}
void setIsRunning(
AsyncTaskInfo &Info,
const AsyncTask<Runtime, ActiveTaskStatusWithoutEscalation<Runtime>>
*Task) {
Info.HasIsRunning = true;
Info.IsRunning =
Task->PrivateStorage.Status.Flags[0] & ActiveTaskStatusFlags::IsRunning;
}
std::pair<bool, uint32_t> getThreadPort(
const AsyncTask<Runtime, ActiveTaskStatusWithEscalation<Runtime>> *Task) {
#if HAS_DISPATCH_LOCK_IS_LOCKED
return {true,
dispatch_lock_owner(Task->PrivateStorage.Status.ExecutionLock[0])};
#else
// The target runtime was built with priority escalation but we don't have
// the swift_concurrency_private.h header needed to decode the lock.
return {false, 0};
#endif
}
std::pair<bool, uint32_t> getThreadPort(
const AsyncTask<Runtime, ActiveTaskStatusWithoutEscalation<Runtime>>
*Task) {
// Tasks without escalation have no thread port to query.
return {false, 0};
}
std::pair<bool, uint32_t> getThreadPort(
const DefaultActorImpl<Runtime, ActiveActorStatusWithEscalation<Runtime>>
*Actor) {
#if HAS_DISPATCH_LOCK_IS_LOCKED
return {true, dispatch_lock_owner(Actor->Status.DrainLock[0])};
#else
// The target runtime was built with priority escalation but we don't have
// the swift_concurrency_private.h header needed to decode the lock.
return {false, 0};
#endif
}
std::pair<bool, uint32_t>
getThreadPort(const DefaultActorImpl<
Runtime, ActiveActorStatusWithoutEscalation<Runtime>> *Actor) {
// Actors without escalation have no thread port to query.
return {false, 0};
}
template <typename AsyncTaskType>
std::pair<std::optional<std::string>, AsyncTaskInfo>
asyncTaskInfo(RemoteAddress AsyncTaskPtr, unsigned ChildTaskLimit,
unsigned AsyncBacktraceLimit) {
auto AsyncTaskObj = readObj<AsyncTaskType>(AsyncTaskPtr);
if (!AsyncTaskObj)
return {std::string("failure reading async task"), {}};
AsyncTaskInfo Info{};
swift::JobFlags JobFlags(AsyncTaskObj->Flags);
Info.Kind = static_cast<unsigned>(JobFlags.getKind());
Info.EnqueuePriority = static_cast<unsigned>(JobFlags.getPriority());
Info.IsChildTask = JobFlags.task_isChildTask();
Info.IsFuture = JobFlags.task_isFuture();
Info.IsGroupChildTask = JobFlags.task_isGroupChildTask();
Info.IsAsyncLetTask = JobFlags.task_isAsyncLetTask();
uint32_t TaskStatusFlags = AsyncTaskObj->PrivateStorage.Status.Flags[0];
Info.IsCancelled = TaskStatusFlags & ActiveTaskStatusFlags::IsCancelled;
Info.IsStatusRecordLocked =
TaskStatusFlags & ActiveTaskStatusFlags::IsStatusRecordLocked;
Info.IsEscalated = TaskStatusFlags & ActiveTaskStatusFlags::IsEscalated;
Info.IsEnqueued = TaskStatusFlags & ActiveTaskStatusFlags::IsEnqueued;
Info.IsComplete = TaskStatusFlags & ActiveTaskStatusFlags::IsComplete;
setIsRunning(Info, AsyncTaskObj.get());
std::tie(Info.HasThreadPort, Info.ThreadPort) =
getThreadPort(AsyncTaskObj.get());
Info.Id =
AsyncTaskObj->Id | ((uint64_t)AsyncTaskObj->PrivateStorage.Id << 32);
Info.AllocatorSlabPtr = AsyncTaskObj->PrivateStorage.Allocator.FirstSlab;
Info.RunJob = getRunJob(AsyncTaskObj.get());
// Find all child tasks.
unsigned ChildTaskLoopCount = 0;
auto RecordPtr = RemoteAddress(AsyncTaskObj->PrivateStorage.Status.Record,
RemoteAddress::DefaultAddressSpace);
while (RecordPtr && ChildTaskLoopCount++ < ChildTaskLimit) {
auto RecordObj = readObj<TaskStatusRecord<Runtime>>(RecordPtr);
if (!RecordObj)
break;
// This cuts off high bits if our size_t doesn't match the target's. We
// only read the Kind bits which are at the bottom, so that's OK here.
// Beware of this when reading anything else.
TaskStatusRecordFlags Flags{RecordObj->Flags};
auto Kind = Flags.getKind();
StoredPointer ChildTask = 0;
if (Kind == TaskStatusRecordKind::ChildTask) {
auto RecordObj = readObj<ChildTaskStatusRecord<Runtime>>(RecordPtr);
if (RecordObj)
ChildTask = RecordObj->FirstChild;
} else if (Kind == TaskStatusRecordKind::TaskGroup) {
auto RecordObj = readObj<TaskGroupTaskStatusRecord<Runtime>>(RecordPtr);
if (RecordObj)
ChildTask = RecordObj->FirstChild;
}
while (ChildTask && ChildTaskLoopCount++ < ChildTaskLimit) {
RemoteAddress ChildTaskAddress =
RemoteAddress(ChildTask, RemoteAddress::DefaultAddressSpace);
// Read the child task.
auto ChildTaskObj = readObj<AsyncTaskType>(ChildTaskAddress);
if (!ChildTaskObj)
return {std::string("found unreadable child task pointer"), Info};
Info.ChildTasks.push_back(ChildTask);
swift::JobFlags ChildJobFlags(AsyncTaskObj->Flags);
if (ChildJobFlags.task_isChildTask()) {
if (asyncTaskSize == 0)
return {std::string("target async task size unknown, unable to "
"iterate child tasks"),
Info};
RemoteAddress ChildFragmentAddr = ChildTaskAddress + asyncTaskSize;
auto ChildFragmentObj =
readObj<ChildFragment<Runtime>>(ChildFragmentAddr);
if (ChildFragmentObj)
ChildTask = ChildFragmentObj->NextChild;
else
ChildTask = 0;
} else {
// No child fragment, so we're done iterating.
ChildTask = 0;
}
}
RecordPtr =
RemoteAddress(RecordObj->Parent, RemoteAddress::DefaultAddressSpace);
}
const auto TaskResumeContext = AsyncTaskObj->ResumeContextAndReserved[0];
Info.ResumeAsyncContext = TaskResumeContext;
// Walk the async backtrace.
if (Info.HasIsRunning && !Info.IsRunning) {
auto ResumeContext = TaskResumeContext;
unsigned AsyncBacktraceLoopCount = 0;
while (ResumeContext && AsyncBacktraceLoopCount++ < AsyncBacktraceLimit) {
auto ResumeContextObj = readObj<AsyncContext<Runtime>>(
RemoteAddress(ResumeContext, RemoteAddress::DefaultAddressSpace));
if (!ResumeContextObj)
break;
Info.AsyncBacktraceFrames.push_back(
stripSignedPointer(ResumeContextObj->ResumeParent).getRawAddress());
ResumeContext =
stripSignedPointer(ResumeContextObj->Parent).getRawAddress();
}
}
return {std::nullopt, Info};
}
template <typename ActorType>
std::pair<std::optional<std::string>, ActorInfo>
actorInfo(RemoteAddress ActorPtr) {
auto ActorObj = readObj<ActorType>(ActorPtr);
if (!ActorObj)
return {std::string("failure reading actor"), {}};
ActorInfo Info{};
uint32_t Flags = ActorObj->Status.Flags[0];
Info.State = Flags & concurrency::ActorFlagConstants::ActorStateMask;
Info.IsPriorityEscalated =
Flags & concurrency::ActorFlagConstants::IsPriorityEscalated;
Info.MaxPriority =
(Flags & concurrency::ActorFlagConstants::PriorityMask) >>
concurrency::ActorFlagConstants::PriorityShift;
Info.IsDistributedRemote = ActorObj->IsDistributedRemote;
// Don't read FirstJob when idle.
if (Info.State != concurrency::ActorFlagConstants::Idle) {
// This is a JobRef which stores flags in the low bits.
Info.FirstJob = ActorObj->Status.FirstJob & ~0x3;
}
std::tie(Info.HasThreadPort, Info.ThreadPort) =
getThreadPort(ActorObj.get());
return {std::nullopt, Info};
}
// Get the most human meaningful "run job" function pointer from the task,
// like AsyncTask::getResumeFunctionForLogging does.
template <typename AsyncTaskType>
StoredPointer getRunJob(const AsyncTaskType *AsyncTaskObj) {
auto Fptr = stripSignedPointer(AsyncTaskObj->RunJob).getRawAddress();
loadTargetPointers();
auto ResumeContextPtr = AsyncTaskObj->ResumeContextAndReserved[0];
if (target_non_future_adapter && Fptr == target_non_future_adapter) {
using Prefix = AsyncContextPrefix<Runtime>;
auto PrefixAddr = ResumeContextPtr - sizeof(Prefix);
auto PrefixBytes = getReader().readBytes(
RemoteAddress(PrefixAddr, RemoteAddress::DefaultAddressSpace),
sizeof(Prefix));
if (PrefixBytes) {
auto PrefixPtr = reinterpret_cast<const Prefix *>(PrefixBytes.get());
return stripSignedPointer(PrefixPtr->AsyncEntryPoint).getRawAddress();
}
} else if (target_future_adapter && Fptr == target_future_adapter) {
using Prefix = FutureAsyncContextPrefix<Runtime>;
auto PrefixAddr = ResumeContextPtr - sizeof(Prefix);
auto PrefixBytes = getReader().readBytes(
RemoteAddress(PrefixAddr, RemoteAddress::DefaultAddressSpace),
sizeof(Prefix));
if (PrefixBytes) {
auto PrefixPtr = reinterpret_cast<const Prefix *>(PrefixBytes.get());
return stripSignedPointer(PrefixPtr->AsyncEntryPoint).getRawAddress();
}
} else if ((target_task_wait_throwing_resume_adapter &&
Fptr == target_task_wait_throwing_resume_adapter) ||
(target_task_future_wait_resume_adapter &&
Fptr == target_task_future_wait_resume_adapter)) {
// It's only safe to look through these adapters when there's a dependency
// record. If there isn't a dependency record, then the task was resumed
// and the pointers are potentially stale.
if (AsyncTaskObj->PrivateStorage.DependencyRecord) {
auto ContextBytes = getReader().readBytes(
RemoteAddress(ResumeContextPtr, RemoteAddress::DefaultAddressSpace),
sizeof(AsyncContext<Runtime>));
if (ContextBytes) {
auto ContextPtr = reinterpret_cast<const AsyncContext<Runtime> *>(
ContextBytes.get());
return stripSignedPointer(ContextPtr->ResumeParent).getRawAddress();
}
}
}
return Fptr;
}
void loadTargetPointers() {
if (setupTargetPointers)
return;
auto getPointer = [&](const std::string &name) -> StoredPointer {
auto Symbol = getReader().getSymbolAddress(name);
if (!Symbol)
return 0;
auto Pointer = getReader().readPointer(Symbol, sizeof(StoredPointer));
if (!Pointer)
return 0;
return Pointer->getResolvedAddress().getRawAddress();
};
target_asyncTaskMetadata =
getPointer("_swift_concurrency_debug_asyncTaskMetadata");
target_non_future_adapter =
getPointer("_swift_concurrency_debug_non_future_adapter");
target_future_adapter =
getPointer("_swift_concurrency_debug_future_adapter");
target_task_wait_throwing_resume_adapter = getPointer(
"_swift_concurrency_debug_task_wait_throwing_resume_adapter");
target_task_future_wait_resume_adapter =
getPointer("_swift_concurrency_debug_task_future_wait_resume_adapter");
auto supportsPriorityEscalationAddr = getReader().getSymbolAddress(
"_swift_concurrency_debug_supportsPriorityEscalation");
if (supportsPriorityEscalationAddr) {
getReader().readInteger(supportsPriorityEscalationAddr,
&supportsPriorityEscalation);
}
auto asyncTaskSizeAddr =
getReader().getSymbolAddress("_swift_concurrency_debug_asyncTaskSize");
if (asyncTaskSizeAddr) {
getReader().readInteger(asyncTaskSizeAddr, &asyncTaskSize);
}
setupTargetPointers = true;
}
const TypeInfo *
getClosureContextInfo(RemoteAddress Context, const ClosureContextInfo &Info,
remote::TypeInfoProvider *ExternalTypeInfo) {
RecordTypeInfoBuilder Builder(getBuilder().getTypeConverter(),
RecordKind::ClosureContext);
auto Metadata = readMetadataFromInstance(Context);
if (!Metadata)
return nullptr;
// Calculate the offset of the first capture.
// See GenHeap.cpp, buildPrivateMetadata().
auto OffsetToFirstCapture =
this->readOffsetToFirstCaptureFromMetadata(*Metadata);
if (!OffsetToFirstCapture)
return nullptr;
// Initialize the builder.
Builder.addField(*OffsetToFirstCapture,
/*alignment=*/sizeof(StoredPointer),
/*numExtraInhabitants=*/0,
/*bitwiseTakable=*/true);
// Skip the closure's necessary bindings struct, if it's present.
auto SizeOfNecessaryBindings = Info.NumBindings * sizeof(StoredPointer);
Builder.addField(/*size=*/SizeOfNecessaryBindings,
/*alignment=*/sizeof(StoredPointer),
/*numExtraInhabitants=*/0,
/*bitwiseTakable=*/true);
// FIXME: should be unordered_set but I'm too lazy to write a hash
// functor
std::set<std::pair<const TypeRef *, const MetadataSource *>> Done;
GenericArgumentMap Subs;
llvm::ArrayRef<const TypeRef *> CaptureTypes = Info.CaptureTypes;
// Closure context element layout depends on the layout of the
// captured types, but captured types might depend on element
// layout (of previous elements). Use an iterative approach to
// solve the problem.
while (!CaptureTypes.empty()) {
const TypeRef *OrigCaptureTR = CaptureTypes[0];
// If we failed to demangle the capture type, we cannot proceed.
if (OrigCaptureTR == nullptr)
return nullptr;
const TypeRef *SubstCaptureTR = nullptr;
// If we have enough substitutions to make this captured value's
// type concrete, or we know it's size anyway (because it is a
// class reference or metatype, for example), go ahead and add
// it to the layout.
if (OrigCaptureTR->isConcreteAfterSubstitutions(Subs))
SubstCaptureTR = OrigCaptureTR->subst(getBuilder(), Subs);
else if (getBuilder().getTypeConverter().hasFixedSize(OrigCaptureTR))
SubstCaptureTR = OrigCaptureTR;
if (SubstCaptureTR != nullptr) {
Builder.addField("", SubstCaptureTR, ExternalTypeInfo);
if (Builder.isInvalid())
return nullptr;
// Try the next capture type.
CaptureTypes = CaptureTypes.slice(1);
continue;
}
// Ok, we do not have enough substitutions yet. Perhaps we have
// enough elements figured out that we can pick off some
// metadata sources though, and use those to derive some new
// substitutions.
bool Progress = false;
for (auto Source : Info.MetadataSources) {
// Don't read a source more than once.
if (Done.count(Source))
continue;
// If we don't have enough information to read this source
// (because it is fulfilled by metadata from a capture at
// at unknown offset), keep going.
if (!isMetadataSourceReady(Source.second, Builder))
continue;
auto Metadata = readMetadataSource(Context, Source.second, Builder);
if (!Metadata)
return nullptr;
auto *SubstTR = readTypeFromMetadata(*Metadata);
if (SubstTR == nullptr)
return nullptr;
if (!TypeRef::deriveSubstitutions(Subs, Source.first, SubstTR))
return nullptr;
Done.insert(Source);
Progress = true;
}
// If we failed to make any forward progress above, we're stuck
// and cannot close out this layout.
if (!Progress)
return nullptr;
}
// Ok, we have a complete picture now.
return Builder.build();
}
/// Checks if we have enough information to read the given metadata
/// source.
///
/// \param Builder Used to obtain offsets of elements known so far.
bool isMetadataSourceReady(const MetadataSource *MS,
const RecordTypeInfoBuilder &Builder) {
if (!MS)
return false;
switch (MS->getKind()) {
case MetadataSourceKind::ClosureBinding:
return true;
case MetadataSourceKind::ReferenceCapture: {
unsigned Index = cast<ReferenceCaptureMetadataSource>(MS)->getIndex();
return Index < Builder.getNumFields();
}
case MetadataSourceKind::MetadataCapture: {
unsigned Index = cast<MetadataCaptureMetadataSource>(MS)->getIndex();
return Index < Builder.getNumFields();
}
case MetadataSourceKind::GenericArgument: {
auto Base = cast<GenericArgumentMetadataSource>(MS)->getSource();
return isMetadataSourceReady(Base, Builder);
}
case MetadataSourceKind::Self:
case MetadataSourceKind::SelfWitnessTable:
return true;
}
swift_unreachable("Unhandled MetadataSourceKind in switch.");
}
/// Read metadata for a captured generic type from a closure context.
///
/// \param Context The closure context in the remote process.
///
/// \param MS The metadata source, which must be "ready" as per the
/// above.
///
/// \param Builder Used to obtain offsets of elements known so far.
std::optional<RemoteAddress>
readMetadataSource(RemoteAddress Context, const MetadataSource *MS,
const RecordTypeInfoBuilder &Builder) {
switch (MS->getKind()) {
case MetadataSourceKind::ClosureBinding: {
unsigned Index = cast<ClosureBindingMetadataSource>(MS)->getIndex();
// Skip the context's HeapObject header
// (one word each for isa pointer and reference counts).
//
// Metadata and conformance tables are stored consecutively after
// the heap object header, in the 'necessary bindings' area.
//
// We should only have the index of a type metadata record here.
unsigned Offset = getSizeOfHeapObject() +
sizeof(StoredPointer) * Index;
RemoteAddress MetadataAddress;
if (!getReader().template readRemoteAddress<StoredPointer>(
Context + Offset, MetadataAddress))
break;
return MetadataAddress;
}
case MetadataSourceKind::ReferenceCapture: {
unsigned Index = cast<ReferenceCaptureMetadataSource>(MS)->getIndex();
// We should already have enough type information to know the offset
// of this capture in the context.
unsigned CaptureOffset = Builder.getFieldOffset(Index);
RemoteAddress CaptureAddress;
if (!getReader().template readRemoteAddress<StoredPointer>(
Context + CaptureOffset, CaptureAddress))
break;
// Read the requested capture's isa pointer.
return readMetadataFromInstance(CaptureAddress);
}
case MetadataSourceKind::MetadataCapture: {
unsigned Index = cast<MetadataCaptureMetadataSource>(MS)->getIndex();
// We should already have enough type information to know the offset
// of this capture in the context.
unsigned CaptureOffset = Builder.getFieldOffset(Index);
RemoteAddress CaptureAddress;
if (!getReader().template readRemoteAddress<StoredPointer>(
Context + CaptureOffset, CaptureAddress))
break;
return CaptureAddress;
}
case MetadataSourceKind::GenericArgument: {
auto *GAMS = cast<GenericArgumentMetadataSource>(MS);
auto Base = readMetadataSource(Context, GAMS->getSource(), Builder);
if (!Base)
break;
unsigned Index = GAMS->getIndex();
auto Arg = readGenericArgFromMetadata(*Base, Index);
if (!Arg)
break;
return *Arg;
}
case MetadataSourceKind::Self:
case MetadataSourceKind::SelfWitnessTable:
break;
}
return std::nullopt;
}
template <typename T>
MemoryReader::ReadObjResult<T> readObj(RemoteAddress Ptr) {
return getReader().template readObj<T>(Ptr);
}
};
} // end namespace reflection
} // end namespace swift
#endif // SWIFT_REFLECTION_REFLECTIONCONTEXT_H