Skip to content

[RemoteMirror][swift-inspect] Add a command to inspect the state of the concurrency runtime. #41160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 219 additions & 11 deletions include/swift/Reflection/ReflectionContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ class ReflectionContext
std::vector<MemoryReader::ReadBytesResult> savedBuffers;
std::vector<std::tuple<RemoteAddress, RemoteAddress>> imageRanges;

bool setupTargetPointers = false;
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;

public:
using super::getBuilder;
using super::readDemanglingForContextDescriptor;
Expand Down Expand Up @@ -137,6 +143,21 @@ class ReflectionContext
std::vector<AsyncTaskAllocationChunk> Chunks;
};

struct AsyncTaskInfo {
uint32_t JobFlags;
uint64_t TaskStatusFlags;
uint64_t Id;
StoredPointer RunJob;
StoredPointer AllocatorSlabPtr;
std::vector<StoredPointer> ChildTasks;
std::vector<StoredPointer> AsyncBacktraceFrames;
};

struct ActorInfo {
StoredSize Flags;
StoredPointer FirstJob;
};

explicit ReflectionContext(std::shared_ptr<MemoryReader> reader)
: super(std::move(reader), *this)
{}
Expand Down Expand Up @@ -1067,6 +1088,31 @@ class ReflectionContext
return dyn_cast_or_null<const RecordTypeInfo>(TypeInfo);
}

bool metadataIsActor(StoredPointer 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;

auto DescriptorBytes =
getReader().readBytes(RemoteAddress(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(StoredPointer NodePtr,
Expand Down Expand Up @@ -1378,22 +1424,179 @@ class ReflectionContext
return {llvm::None, {Slab->Next, SlabSize, {Chunk}}};
}

std::pair<llvm::Optional<std::string>, StoredPointer>
asyncTaskSlabPtr(StoredPointer AsyncTaskPtr) {
using AsyncTask = AsyncTask<Runtime>;

auto AsyncTaskBytes =
getReader().readBytes(RemoteAddress(AsyncTaskPtr), sizeof(AsyncTask));
auto *AsyncTaskObj =
reinterpret_cast<const AsyncTask *>(AsyncTaskBytes.get());
std::pair<llvm::Optional<std::string>, AsyncTaskInfo>
asyncTaskInfo(StoredPointer AsyncTaskPtr) {
auto AsyncTaskObj = readObj<AsyncTask<Runtime>>(AsyncTaskPtr);
if (!AsyncTaskObj)
return {std::string("failure reading async task"), 0};
return {std::string("failure reading async task"), {}};

AsyncTaskInfo Info{};
Info.JobFlags = AsyncTaskObj->Flags;
Info.TaskStatusFlags = AsyncTaskObj->PrivateStorage.Status.Flags;
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.
auto RecordPtr = AsyncTaskObj->PrivateStorage.Status.Record;
while (RecordPtr) {
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) {
Info.ChildTasks.push_back(ChildTask);

StoredPointer ChildFragmentAddr =
ChildTask + sizeof(AsyncTask<Runtime>);
auto ChildFragmentObj =
readObj<ChildFragment<Runtime>>(ChildFragmentAddr);
if (ChildFragmentObj)
ChildTask = ChildFragmentObj->NextChild;
else
ChildTask = 0;
}

RecordPtr = RecordObj->Parent;
}

// Walk the async backtrace if the task isn't running or cancelled.
// TODO: Use isEnqueued from https://github.com/apple/swift/pull/41088/ once
// that's available.
int IsCancelledFlag = 0x100;
int IsRunningFlag = 0x800;
if (!(AsyncTaskObj->PrivateStorage.Status.Flags & IsCancelledFlag) &&
!(AsyncTaskObj->PrivateStorage.Status.Flags & IsRunningFlag)) {
auto ResumeContext = AsyncTaskObj->ResumeContextAndReserved[0];
while (ResumeContext) {
auto ResumeContextObj = readObj<AsyncContext<Runtime>>(ResumeContext);
if (!ResumeContextObj)
break;
Info.AsyncBacktraceFrames.push_back(
stripSignedPointer(ResumeContextObj->ResumeParent));
ResumeContext = stripSignedPointer(ResumeContextObj->Parent);
}
}

return {llvm::None, Info};
}

std::pair<llvm::Optional<std::string>, ActorInfo>
actorInfo(StoredPointer ActorPtr) {
using DefaultActorImpl = DefaultActorImpl<Runtime>;

auto ActorObj = readObj<DefaultActorImpl>(ActorPtr);
if (!ActorObj)
return {std::string("failure reading actor"), {}};

ActorInfo Info{};
Info.Flags = ActorObj->Flags;

// Status is the low 3 bits of Flags. Status of 0 is Idle. Don't read
// FirstJob when idle.
auto Status = Info.Flags & 0x7;
if (Status != 0) {
// This is a JobRef which stores flags in the low bits.
Info.FirstJob = ActorObj->FirstJob & ~StoredPointer(0x3);
}
return {llvm::None, Info};
}

StoredPointer SlabPtr = AsyncTaskObj->PrivateStorage.Allocator.FirstSlab;
return {llvm::None, SlabPtr};
StoredPointer nextJob(StoredPointer JobPtr) {
using Job = Job<Runtime>;

auto JobBytes = getReader().readBytes(RemoteAddress(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:
// Get the most human meaningful "run job" function pointer from the task,
// like AsyncTask::getResumeFunctionForLogging does.
StoredPointer getRunJob(const AsyncTask<Runtime> *AsyncTaskObj) {
auto Fptr = stripSignedPointer(AsyncTaskObj->RunJob);

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), sizeof(Prefix));
if (PrefixBytes) {
auto PrefixPtr = reinterpret_cast<const Prefix *>(PrefixBytes.get());
return stripSignedPointer(PrefixPtr->AsyncEntryPoint);
}
} else if (target_future_adapter && Fptr == target_future_adapter) {
using Prefix = FutureAsyncContextPrefix<Runtime>;
auto PrefixAddr = ResumeContextPtr - sizeof(Prefix);
auto PrefixBytes =
getReader().readBytes(RemoteAddress(PrefixAddr), sizeof(Prefix));
if (PrefixBytes) {
auto PrefixPtr = reinterpret_cast<const Prefix *>(PrefixBytes.get());
return stripSignedPointer(PrefixPtr->AsyncEntryPoint);
}
} 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)) {
auto ContextBytes = getReader().readBytes(RemoteAddress(ResumeContextPtr),
sizeof(AsyncContext<Runtime>));
if (ContextBytes) {
auto ContextPtr =
reinterpret_cast<const AsyncContext<Runtime> *>(ContextBytes.get());
return stripSignedPointer(ContextPtr->ResumeParent);
}
}

return Fptr;
}

void loadTargetPointers() {
if (setupTargetPointers)
return;

auto getFunc = [&](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().getAddressData();
};
target_non_future_adapter =
getFunc("_swift_concurrency_debug_non_future_adapter");
target_future_adapter = getFunc("_swift_concurrency_debug_future_adapter");
target_task_wait_throwing_resume_adapter =
getFunc("_swift_concurrency_debug_task_wait_throwing_resume_adapter");
target_task_future_wait_resume_adapter =
getFunc("_swift_concurrency_debug_task_future_wait_resume_adapter");
setupTargetPointers = true;
}

const TypeInfo *
getClosureContextInfo(StoredPointer Context, const ClosureContextInfo &Info,
remote::TypeInfoProvider *ExternalTypeInfo) {
Expand Down Expand Up @@ -1615,6 +1818,11 @@ class ReflectionContext

return llvm::None;
}

template <typename T>
MemoryReader::ReadObjResult<T> readObj(StoredPointer Ptr) {
return getReader().template readObj<T>(RemoteAddress(Ptr));
}
};

} // end namespace reflection
Expand Down
60 changes: 58 additions & 2 deletions include/swift/Reflection/RuntimeInternals.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ struct Job {
uint32_t Flags;
uint32_t Id;
typename Runtime::StoredPointer Reserved[2];
typename Runtime::StoredPointer RunJob;
typename Runtime::StoredSignedPointer RunJob;
};

template <typename Runtime>
Expand Down Expand Up @@ -104,6 +104,8 @@ struct AsyncTaskPrivateStorage {
ActiveTaskStatus<Runtime> Status;
StackAllocator<Runtime> Allocator;
typename Runtime::StoredPointer Local;
typename Runtime::StoredPointer ExclusivityAccessSet[2];
uint32_t Id;
};

template <typename Runtime>
Expand All @@ -112,7 +114,61 @@ struct AsyncTask: Job<Runtime> {
typename Runtime::StoredPointer ResumeContextAndReserved[
sizeof(typename Runtime::StoredPointer) == 8 ? 2 : 1];

AsyncTaskPrivateStorage<Runtime> PrivateStorage;
union {
AsyncTaskPrivateStorage<Runtime> PrivateStorage;
typename Runtime::StoredPointer PrivateStorageRaw[14];
};
};

template <typename Runtime>
struct AsyncContext {
typename Runtime::StoredSignedPointer Parent;
typename Runtime::StoredSignedPointer ResumeParent;
uint32_t Flags;
};

template <typename Runtime>
struct AsyncContextPrefix {
typename Runtime::StoredSignedPointer AsyncEntryPoint;
typename Runtime::StoredPointer ClosureContext;
typename Runtime::StoredPointer ErrorResult;
};

template <typename Runtime>
struct FutureAsyncContextPrefix {
typename Runtime::StoredPointer IndirectResult;
typename Runtime::StoredSignedPointer AsyncEntryPoint;
typename Runtime::StoredPointer ClosureContext;
typename Runtime::StoredPointer ErrorResult;
};

template <typename Runtime>
struct DefaultActorImpl {
HeapObject<Runtime> HeapObject;
typename Runtime::StoredPointer FirstJob;
typename Runtime::StoredSize Flags;
};

template <typename Runtime>
struct TaskStatusRecord {
typename Runtime::StoredSize Flags;
typename Runtime::StoredPointer Parent;
};

template <typename Runtime>
struct ChildTaskStatusRecord : TaskStatusRecord<Runtime> {
typename Runtime::StoredPointer FirstChild;
};

template <typename Runtime>
struct TaskGroupTaskStatusRecord : TaskStatusRecord<Runtime> {
typename Runtime::StoredPointer FirstChild;
};

template <typename Runtime>
struct ChildFragment {
typename Runtime::StoredPointer Parent;
typename Runtime::StoredPointer NextChild;
};

} // end namespace reflection
Expand Down
13 changes: 13 additions & 0 deletions include/swift/Remote/MemoryReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ class MemoryReader {
using ReadBytesResult =
std::unique_ptr<const void, std::function<void(const void *)>>;

template <typename T>
using ReadObjResult =
std::unique_ptr<const T, std::function<void(const void *)>>;

virtual bool queryDataLayout(DataLayoutQueryType type, void *inBuffer,
void *outBuffer) = 0;

Expand Down Expand Up @@ -90,6 +94,15 @@ class MemoryReader {
return true;
}

template <typename T>
ReadObjResult<T> readObj(RemoteAddress address) {
auto bytes = readBytes(address, sizeof(T));
auto deleter = bytes.get_deleter();
auto ptr = bytes.get();
bytes.release();
return ReadObjResult<T>(reinterpret_cast<const T *>(ptr), deleter);
}

/// Attempts to read 'size' bytes from the given address in the remote process.
///
/// Returns a pointer to the requested data and a function that must be called to
Expand Down
Loading