mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[RemoteMirror][swift-inspect] Add a command to inspect the state of the concurrency runtime.
Most of the new inspection logic is in Remote Mirror. New code in swift-inspect calls the new Remote Mirror functions and formats the resulting information for display. Specific Remote Mirror changes: * Add a call to check if a given metadata is an actor. * Add calls to get information about actors and tasks. * Add a `readObj` call to MemoryReader that combines the read and the cast, greatly simplifying code chasing pointers in the remote process. * Add a generalized facility to the C shims that can allocate a temporary object that remains valid until at least the next call, which is used to return various temporary arrays from the new calls. Remove the existing `lastString` and `lastChunks` member variables in favor of this new facility. Swift-inspect changes: * Add a new dump-concurrency command. * Add a new `ConcurrencyDumper.swift` file with the implementation. The dumper needs to do some additional work with the results from Remote Mirror to build up the task tree and this keeps it all organized. * Extend `Inspector` to query the target's threads and fetch each thread's current task. Concurrency runtime changes: * Add `_swift_concurrency_debug` variables pointing to the various future adapters. Remote Mirror uses these to provide a better view of a tasks's resume pointer. rdar://85231338
This commit is contained in:
@@ -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;
|
||||
@@ -137,6 +143,21 @@ public:
|
||||
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)
|
||||
{}
|
||||
@@ -1067,6 +1088,31 @@ public:
|
||||
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,
|
||||
@@ -1378,22 +1424,179 @@ public:
|
||||
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"), {}};
|
||||
|
||||
StoredPointer SlabPtr = AsyncTaskObj->PrivateStorage.Allocator.FirstSlab;
|
||||
return {llvm::None, SlabPtr};
|
||||
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 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) {
|
||||
@@ -1615,6 +1818,11 @@ private:
|
||||
|
||||
return llvm::None;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
MemoryReader::ReadObjResult<T> readObj(StoredPointer Ptr) {
|
||||
return getReader().template readObj<T>(RemoteAddress(Ptr));
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace reflection
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -112,7 +114,61 @@ struct AsyncTask: Job<Runtime> {
|
||||
typename Runtime::StoredPointer ResumeContextAndReserved[
|
||||
sizeof(typename Runtime::StoredPointer) == 8 ? 2 : 1];
|
||||
|
||||
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
|
||||
|
||||
@@ -40,6 +40,10 @@ public:
|
||||
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;
|
||||
|
||||
@@ -90,6 +94,15 @@ public:
|
||||
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
|
||||
|
||||
@@ -151,6 +151,11 @@ swift_reflection_metadataNominalTypeDescriptor(SwiftReflectionContextRef Context
|
||||
swift_reflection_ptr_t Metadata);
|
||||
|
||||
|
||||
SWIFT_REMOTE_MIRROR_LINKAGE
|
||||
int
|
||||
swift_reflection_metadataIsActor(SwiftReflectionContextRef ContextRef,
|
||||
swift_reflection_ptr_t Metadata);
|
||||
|
||||
/// Returns an opaque type reference for a class or closure context
|
||||
/// instance pointer, or NULL if one can't be constructed.
|
||||
///
|
||||
@@ -435,6 +440,21 @@ swift_async_task_slab_allocations_return_t
|
||||
swift_reflection_asyncTaskSlabAllocations(SwiftReflectionContextRef ContextRef,
|
||||
swift_reflection_ptr_t SlabPtr);
|
||||
|
||||
SWIFT_REMOTE_MIRROR_LINKAGE
|
||||
swift_async_task_info_t
|
||||
swift_reflection_asyncTaskInfo(SwiftReflectionContextRef ContextRef,
|
||||
swift_reflection_ptr_t AsyncTaskPtr);
|
||||
|
||||
SWIFT_REMOTE_MIRROR_LINKAGE
|
||||
swift_actor_info_t
|
||||
swift_reflection_actorInfo(SwiftReflectionContextRef ContextRef,
|
||||
swift_reflection_ptr_t ActorPtr);
|
||||
|
||||
SWIFT_REMOTE_MIRROR_LINKAGE
|
||||
swift_reflection_ptr_t
|
||||
swift_reflection_nextJob(SwiftReflectionContextRef ContextRef,
|
||||
swift_reflection_ptr_t JobPtr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
@@ -225,6 +225,35 @@ typedef struct swift_async_task_slab_allocations_return {
|
||||
swift_async_task_allocation_chunk_t *Chunks;
|
||||
} swift_async_task_slab_allocations_return_t;
|
||||
|
||||
typedef struct swift_async_task_info {
|
||||
/// On failure, a pointer to a string describing the error. On success, NULL.
|
||||
/// This pointer remains valid until the next
|
||||
/// swift_reflection call on the given context.
|
||||
const char *Error;
|
||||
|
||||
uint32_t JobFlags;
|
||||
uint64_t TaskStatusFlags;
|
||||
uint64_t Id;
|
||||
swift_reflection_ptr_t RunJob;
|
||||
swift_reflection_ptr_t AllocatorSlabPtr;
|
||||
|
||||
unsigned ChildTaskCount;
|
||||
swift_reflection_ptr_t *ChildTasks;
|
||||
|
||||
unsigned AsyncBacktraceFramesCount;
|
||||
swift_reflection_ptr_t *AsyncBacktraceFrames;
|
||||
} swift_async_task_info_t;
|
||||
|
||||
typedef struct swift_actor_info {
|
||||
/// On failure, a pointer to a string describing the error. On success, NULL.
|
||||
/// This pointer remains valid until the next
|
||||
/// swift_reflection call on the given context.
|
||||
const char *Error;
|
||||
|
||||
uint64_t Flags;
|
||||
swift_reflection_ptr_t FirstJob;
|
||||
} swift_actor_info_t;
|
||||
|
||||
/// An opaque pointer to a context which maintains state and
|
||||
/// caching of reflection structure for heap instances.
|
||||
typedef struct SwiftReflectionContext *SwiftReflectionContextRef;
|
||||
|
||||
@@ -36,6 +36,15 @@ const void *const _swift_concurrency_debug_asyncTaskMetadata;
|
||||
SWIFT_EXPORT_FROM(swift_Concurrency)
|
||||
const void *const _swift_concurrency_debug_asyncTaskSlabMetadata;
|
||||
|
||||
SWIFT_EXPORT_FROM(swift_Concurrency)
|
||||
const void *const _swift_concurrency_debug_non_future_adapter;
|
||||
SWIFT_EXPORT_FROM(swift_Concurrency)
|
||||
const void *const _swift_concurrency_debug_future_adapter;
|
||||
SWIFT_EXPORT_FROM(swift_Concurrency)
|
||||
const void *const _swift_concurrency_debug_task_wait_throwing_resume_adapter;
|
||||
SWIFT_EXPORT_FROM(swift_Concurrency)
|
||||
const void *const _swift_concurrency_debug_task_future_wait_resume_adapter;
|
||||
|
||||
} // namespace swift
|
||||
|
||||
#endif
|
||||
|
||||
@@ -450,6 +450,17 @@ task_future_wait_resume_adapter(SWIFT_ASYNC_CONTEXT AsyncContext *_context) {
|
||||
return _context->ResumeParent(_context->Parent);
|
||||
}
|
||||
|
||||
const void *const swift::_swift_concurrency_debug_non_future_adapter =
|
||||
reinterpret_cast<void *>(non_future_adapter);
|
||||
const void *const swift::_swift_concurrency_debug_future_adapter =
|
||||
reinterpret_cast<void *>(future_adapter);
|
||||
const void
|
||||
*const swift::_swift_concurrency_debug_task_wait_throwing_resume_adapter =
|
||||
reinterpret_cast<void *>(task_wait_throwing_resume_adapter);
|
||||
const void
|
||||
*const swift::_swift_concurrency_debug_task_future_wait_resume_adapter =
|
||||
reinterpret_cast<void *>(task_future_wait_resume_adapter);
|
||||
|
||||
const void *AsyncTask::getResumeFunctionForLogging() {
|
||||
if (ResumeTask == non_future_adapter) {
|
||||
auto asyncContextPrefix = reinterpret_cast<AsyncContextPrefix *>(
|
||||
|
||||
@@ -12,6 +12,7 @@ if(SWIFT_BUILD_DYNAMIC_STDLIB)
|
||||
${SWIFT_RUNTIME_LINK_FLAGS}
|
||||
INCORPORATE_OBJECT_LIBRARIES swiftLLVMSupport
|
||||
SWIFT_COMPILE_FLAGS ${SWIFT_STANDARD_LIBRARY_SWIFT_FLAGS}
|
||||
DARWIN_INSTALL_NAME_DIR "${SWIFTLIB_DARWIN_INSTALL_NAME_DIR}"
|
||||
INSTALL_IN_COMPONENT
|
||||
swift-remote-mirror)
|
||||
endif()
|
||||
|
||||
@@ -47,8 +47,8 @@ struct SwiftReflectionContext {
|
||||
NativeReflectionContext *nativeContext;
|
||||
std::vector<std::function<void()>> freeFuncs;
|
||||
std::vector<std::tuple<swift_addr_t, swift_addr_t>> dataSegments;
|
||||
std::string lastString;
|
||||
std::vector<swift_async_task_allocation_chunk_t> lastChunks;
|
||||
|
||||
std::function<void(void)> freeTemporaryAllocation = [] {};
|
||||
|
||||
SwiftReflectionContext(MemoryReaderImpl impl) {
|
||||
auto Reader = std::make_shared<CMemoryReader>(impl);
|
||||
@@ -56,10 +56,38 @@ struct SwiftReflectionContext {
|
||||
}
|
||||
|
||||
~SwiftReflectionContext() {
|
||||
freeTemporaryAllocation();
|
||||
delete nativeContext;
|
||||
for (auto f : freeFuncs)
|
||||
f();
|
||||
}
|
||||
|
||||
// Allocate a single temporary object that will stay allocated until the next
|
||||
// call to this method, or until the context is destroyed.
|
||||
template <typename T>
|
||||
T *allocateTemporaryObject() {
|
||||
freeTemporaryAllocation();
|
||||
T *obj = new T;
|
||||
freeTemporaryAllocation = [obj] { delete obj; };
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Allocate a single temporary object that will stay allocated until the next
|
||||
// call to allocateTemporaryObject, or until the context is destroyed. Does
|
||||
// NOT free any existing objects created with allocateTemporaryObject or
|
||||
// allocateSubsequentTemporaryObject. Use to allocate additional objects after
|
||||
// a call to allocateTemporaryObject when muliple objects are needed
|
||||
// simultaneously.
|
||||
template <typename T>
|
||||
T *allocateSubsequentTemporaryObject() {
|
||||
T *obj = new T;
|
||||
auto oldFree = freeTemporaryAllocation;
|
||||
freeTemporaryAllocation = [obj, oldFree] {
|
||||
delete obj;
|
||||
oldFree();
|
||||
};
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -297,6 +325,12 @@ swift_reflection_metadataNominalTypeDescriptor(SwiftReflectionContextRef Context
|
||||
return Context->nominalTypeDescriptorFromMetadata(MetadataAddress);
|
||||
}
|
||||
|
||||
int swift_reflection_metadataIsActor(SwiftReflectionContextRef ContextRef,
|
||||
swift_reflection_ptr_t Metadata) {
|
||||
auto Context = ContextRef->nativeContext;
|
||||
return Context->metadataIsActor(Metadata);
|
||||
}
|
||||
|
||||
swift_typeref_t
|
||||
swift_reflection_typeRefForInstance(SwiftReflectionContextRef ContextRef,
|
||||
uintptr_t Object) {
|
||||
@@ -505,8 +539,9 @@ static swift_layout_kind_t convertAllocationChunkKind(
|
||||
static const char *returnableCString(SwiftReflectionContextRef ContextRef,
|
||||
llvm::Optional<std::string> String) {
|
||||
if (String) {
|
||||
ContextRef->lastString = *String;
|
||||
return ContextRef->lastString.c_str();
|
||||
auto *TmpStr = ContextRef->allocateTemporaryObject<std::string>();
|
||||
*TmpStr = *String;
|
||||
return TmpStr->c_str();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -782,14 +817,10 @@ const char *swift_reflection_iterateMetadataAllocationBacktraces(
|
||||
swift_async_task_slab_return_t
|
||||
swift_reflection_asyncTaskSlabPointer(SwiftReflectionContextRef ContextRef,
|
||||
swift_reflection_ptr_t AsyncTaskPtr) {
|
||||
auto Context = ContextRef->nativeContext;
|
||||
llvm::Optional<std::string> Error;
|
||||
NativeReflectionContext::StoredPointer SlabPtr;
|
||||
std::tie(Error, SlabPtr) = Context->asyncTaskSlabPtr(AsyncTaskPtr);
|
||||
|
||||
auto Info = swift_reflection_asyncTaskInfo(ContextRef, AsyncTaskPtr);
|
||||
swift_async_task_slab_return_t Result = {};
|
||||
Result.Error = returnableCString(ContextRef, Error);
|
||||
Result.SlabPtr = SlabPtr;
|
||||
Result.Error = Info.Error;
|
||||
Result.SlabPtr = Info.AllocatorSlabPtr;
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -802,23 +833,88 @@ swift_reflection_asyncTaskSlabAllocations(SwiftReflectionContextRef ContextRef,
|
||||
std::tie(Error, Info) = Context->asyncTaskSlabAllocations(SlabPtr);
|
||||
|
||||
swift_async_task_slab_allocations_return_t Result = {};
|
||||
if (Result.Error) {
|
||||
Result.Error = returnableCString(ContextRef, Error);
|
||||
return Result;
|
||||
}
|
||||
|
||||
Result.NextSlab = Info.NextSlab;
|
||||
Result.SlabSize = Info.SlabSize;
|
||||
|
||||
ContextRef->lastChunks.clear();
|
||||
ContextRef->lastChunks.reserve(Info.Chunks.size());
|
||||
auto *Chunks = ContextRef->allocateTemporaryObject<
|
||||
std::vector<swift_async_task_allocation_chunk_t>>();
|
||||
Chunks->reserve(Info.Chunks.size());
|
||||
for (auto &Chunk : Info.Chunks) {
|
||||
swift_async_task_allocation_chunk_t ConvertedChunk;
|
||||
ConvertedChunk.Start = Chunk.Start;
|
||||
ConvertedChunk.Length = Chunk.Length;
|
||||
ConvertedChunk.Kind = convertAllocationChunkKind(Chunk.Kind);
|
||||
ContextRef->lastChunks.push_back(ConvertedChunk);
|
||||
Chunks->push_back(ConvertedChunk);
|
||||
}
|
||||
|
||||
Result.ChunkCount = ContextRef->lastChunks.size();
|
||||
Result.Chunks = ContextRef->lastChunks.data();
|
||||
Result.ChunkCount = Chunks->size();
|
||||
Result.Chunks = Chunks->data();
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
swift_async_task_info_t
|
||||
swift_reflection_asyncTaskInfo(SwiftReflectionContextRef ContextRef,
|
||||
swift_reflection_ptr_t AsyncTaskPtr) {
|
||||
auto Context = ContextRef->nativeContext;
|
||||
llvm::Optional<std::string> Error;
|
||||
NativeReflectionContext::AsyncTaskInfo TaskInfo;
|
||||
std::tie(Error, TaskInfo) = Context->asyncTaskInfo(AsyncTaskPtr);
|
||||
|
||||
swift_async_task_info_t Result = {};
|
||||
if (Error) {
|
||||
Result.Error = returnableCString(ContextRef, Error);
|
||||
return Result;
|
||||
}
|
||||
Result.JobFlags = TaskInfo.JobFlags;
|
||||
Result.TaskStatusFlags = TaskInfo.TaskStatusFlags;
|
||||
Result.Id = TaskInfo.Id;
|
||||
Result.RunJob = TaskInfo.RunJob;
|
||||
Result.AllocatorSlabPtr = TaskInfo.AllocatorSlabPtr;
|
||||
|
||||
auto *ChildTasks =
|
||||
ContextRef
|
||||
->allocateTemporaryObject<std::vector<swift_reflection_ptr_t>>();
|
||||
std::copy(TaskInfo.ChildTasks.begin(), TaskInfo.ChildTasks.end(),
|
||||
std::back_inserter(*ChildTasks));
|
||||
Result.ChildTaskCount = ChildTasks->size();
|
||||
Result.ChildTasks = ChildTasks->data();
|
||||
|
||||
auto *AsyncBacktraceFrames = ContextRef->allocateSubsequentTemporaryObject<
|
||||
std::vector<swift_reflection_ptr_t>>();
|
||||
std::copy(TaskInfo.AsyncBacktraceFrames.begin(),
|
||||
TaskInfo.AsyncBacktraceFrames.end(),
|
||||
std::back_inserter(*AsyncBacktraceFrames));
|
||||
Result.AsyncBacktraceFramesCount = AsyncBacktraceFrames->size();
|
||||
Result.AsyncBacktraceFrames = AsyncBacktraceFrames->data();
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
swift_actor_info_t
|
||||
swift_reflection_actorInfo(SwiftReflectionContextRef ContextRef,
|
||||
swift_reflection_ptr_t ActorPtr) {
|
||||
auto Context = ContextRef->nativeContext;
|
||||
llvm::Optional<std::string> Error;
|
||||
NativeReflectionContext::ActorInfo TaskInfo;
|
||||
std::tie(Error, TaskInfo) = Context->actorInfo(ActorPtr);
|
||||
|
||||
swift_actor_info_t Result = {};
|
||||
Result.Error = returnableCString(ContextRef, Error);
|
||||
Result.Flags = TaskInfo.Flags;
|
||||
Result.FirstJob = TaskInfo.FirstJob;
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
swift_reflection_ptr_t
|
||||
swift_reflection_nextJob(SwiftReflectionContextRef ContextRef,
|
||||
swift_reflection_ptr_t JobPtr) {
|
||||
auto Context = ContextRef->nativeContext;
|
||||
return Context->nextJob(JobPtr);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
// RUN: -e _ZNSt6vectorIjSaIjEE13_M_insert_auxIJRKjEEEvN9__gnu_cxx17__normal_iteratorIPjS1_EEDpOT_ \
|
||||
// RUN: -e _ZNSt6vectorIjSaIjEE13_M_insert_auxIJjEEEvN9__gnu_cxx17__normal_iteratorIPjS1_EEDpOT_ \
|
||||
// RUN: -e _ZNSt6vectorIjSaIjEE17_M_realloc_insertIJjEEEvN9__gnu_cxx17__normal_iteratorIPjS1_EEDpOT_ \
|
||||
// RUN: -e _ZNSt6vectorImSaImEE17_M_realloc_insertIJRKmEEEvN9__gnu_cxx17__normal_iteratorIPmS1_EEDpOT_ \
|
||||
// RUN: -e _ZNSt6vectorISt10unique_ptrIKvSt8functionIFvPS1_EEESaIS6_EE19_M_emplace_back_auxIJS6_EEEvDpOT_ \
|
||||
// RUN: -e _ZNSt6vectorISt10unique_ptrIKvSt8functionIFvPS1_EEESaIS6_EE17_M_realloc_insertIJS6_EEEvN9__gnu_cxx17__normal_iteratorIPS6_S8_EEDpOT_ \
|
||||
// RUN: -e _ZNSt10_HashtableImSt4pairIKmSt10unique_ptrIKvSt8functionIFvPS3_EEEESaIS9_ENSt8__detail10_Select1stESt8equal_toImESt4hashImENSB_18_Mod_range_hashingENSB_20_Default_ranged_hashENSB_20_Prime_rehash_policyENSB_17_Hashtable_traitsILb0ELb0ELb1EEEE10_M_emplaceIJS0_ImS8_EEEES0_INSB_14_Node_iteratorIS9_Lb0ELb0EEEbESt17integral_constantIbLb1EEDpOT_ \
|
||||
|
||||
401
tools/swift-inspect/Sources/swift-inspect/DumpConcurrency.swift
Normal file
401
tools/swift-inspect/Sources/swift-inspect/DumpConcurrency.swift
Normal file
@@ -0,0 +1,401 @@
|
||||
import SwiftRemoteMirror
|
||||
|
||||
func dumpConcurrency(
|
||||
context: SwiftReflectionContextRef,
|
||||
inspector: Inspector
|
||||
) throws {
|
||||
let dumper = ConcurrencyDumper(context: context, inspector: inspector)
|
||||
dumper.dumpTasks()
|
||||
dumper.dumpActors()
|
||||
dumper.dumpThreads()
|
||||
}
|
||||
|
||||
fileprivate class ConcurrencyDumper {
|
||||
let context: SwiftReflectionContextRef
|
||||
let inspector: Inspector
|
||||
let jobMetadata: swift_reflection_ptr_t?
|
||||
let taskMetadata: swift_reflection_ptr_t?
|
||||
|
||||
struct TaskInfo {
|
||||
var address: swift_reflection_ptr_t
|
||||
var jobFlags: UInt32
|
||||
var taskStatusFlags: UInt64
|
||||
var id: UInt64
|
||||
var runJob: swift_reflection_ptr_t
|
||||
var allocatorSlabPtr: swift_reflection_ptr_t
|
||||
var allocatorTotalSize: Int
|
||||
var allocatorTotalChunks: Int
|
||||
var childTasks: [swift_reflection_ptr_t]
|
||||
var asyncBacktrace: [swift_reflection_ptr_t]
|
||||
var parent: swift_reflection_ptr_t?
|
||||
}
|
||||
|
||||
struct HeapInfo {
|
||||
var tasks: [swift_reflection_ptr_t] = []
|
||||
var jobs: [swift_reflection_ptr_t] = []
|
||||
var actors: [swift_reflection_ptr_t] = []
|
||||
}
|
||||
|
||||
lazy var heapInfo: HeapInfo = gatherHeapInfo()
|
||||
|
||||
lazy var threadCurrentTasks = inspector.threadCurrentTasks().filter{ $0.currentTask != 0 }
|
||||
|
||||
lazy var tasks: [swift_reflection_ptr_t: TaskInfo] = gatherTasks()
|
||||
|
||||
var actors: [swift_reflection_ptr_t] {
|
||||
heapInfo.actors
|
||||
}
|
||||
|
||||
var metadataIsActorCache: [swift_reflection_ptr_t: Bool] = [:]
|
||||
var metadataNameCache: [swift_reflection_ptr_t: String?] = [:]
|
||||
|
||||
init(context: SwiftReflectionContextRef, inspector: Inspector) {
|
||||
self.context = context
|
||||
self.inspector = inspector
|
||||
|
||||
func getMetadata(symbolName: String) -> swift_reflection_ptr_t? {
|
||||
let addr = inspector.getAddr(symbolName: symbolName)
|
||||
if let ptr = inspector.read(address: addr, size: MemoryLayout<UInt>.size) {
|
||||
return swift_reflection_ptr_t(ptr.load(as: UInt.self))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
jobMetadata = getMetadata(symbolName: "_swift_concurrency_debug_jobMetadata")
|
||||
taskMetadata = getMetadata(symbolName: "_swift_concurrency_debug_asyncTaskMetadata")
|
||||
}
|
||||
|
||||
func gatherHeapInfo() -> HeapInfo {
|
||||
var result = HeapInfo()
|
||||
|
||||
inspector.enumerateMallocs { (pointer, size) in
|
||||
let metadata = swift_reflection_ptr_t(swift_reflection_metadataForObject(context, UInt(pointer)))
|
||||
if metadata == jobMetadata {
|
||||
result.jobs.append(pointer)
|
||||
} else if metadata == taskMetadata {
|
||||
result.tasks.append(pointer)
|
||||
} else if isActorMetadata(metadata) {
|
||||
result.actors.append(pointer)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func gatherTasks() -> [swift_reflection_ptr_t: TaskInfo] {
|
||||
var map: [swift_reflection_ptr_t: TaskInfo] = [:]
|
||||
var tasksToScan: Set<swift_reflection_ptr_t> = []
|
||||
tasksToScan.formUnion(heapInfo.tasks)
|
||||
tasksToScan.formUnion(threadCurrentTasks.map{ $0.currentTask }.filter{ $0 != 0 })
|
||||
|
||||
while !tasksToScan.isEmpty {
|
||||
let taskToScan = tasksToScan.removeFirst()
|
||||
if let info = info(forTask: taskToScan) {
|
||||
map[taskToScan] = info
|
||||
for child in info.childTasks {
|
||||
let childMetadata = swift_reflection_metadataForObject(context, UInt(child))
|
||||
if let taskMetadata = taskMetadata, childMetadata != taskMetadata {
|
||||
print("Inconsistent data detected! Child task \(hex: child) has unknown metadata \(hex: taskMetadata)")
|
||||
}
|
||||
if map[child] == nil {
|
||||
tasksToScan.insert(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (task, info) in map {
|
||||
for child in info.childTasks {
|
||||
map[child]?.parent = task
|
||||
}
|
||||
}
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
func isActorMetadata(_ metadata: swift_reflection_ptr_t) -> Bool {
|
||||
if let cached = metadataIsActorCache[metadata] {
|
||||
return cached
|
||||
}
|
||||
let result = swift_reflection_metadataIsActor(context, metadata) != 0
|
||||
metadataIsActorCache[metadata] = result
|
||||
return result
|
||||
}
|
||||
|
||||
func name(metadata: swift_reflection_ptr_t) -> String? {
|
||||
if let cached = metadataNameCache[metadata] {
|
||||
return cached
|
||||
}
|
||||
|
||||
let name = context.name(metadata: metadata)
|
||||
metadataNameCache[metadata] = name
|
||||
return name
|
||||
}
|
||||
|
||||
func info(forTask task: swift_reflection_ptr_t) -> TaskInfo? {
|
||||
let reflectionInfo = swift_reflection_asyncTaskInfo(context, task)
|
||||
if let error = reflectionInfo.Error {
|
||||
print("Error getting info for async task \(hex: task): \(String(cString: error))")
|
||||
return nil
|
||||
}
|
||||
|
||||
// These arrays are temporary pointers which we must copy out before we call
|
||||
// into Remote Mirror again.
|
||||
let children = Array(UnsafeBufferPointer(
|
||||
start: reflectionInfo.ChildTasks,
|
||||
count: Int(reflectionInfo.ChildTaskCount)))
|
||||
let asyncBacktraceFrames = Array(UnsafeBufferPointer(
|
||||
start: reflectionInfo.AsyncBacktraceFrames,
|
||||
count: Int(reflectionInfo.AsyncBacktraceFramesCount)))
|
||||
|
||||
var allocatorSlab = reflectionInfo.AllocatorSlabPtr
|
||||
var allocatorTotalSize = 0
|
||||
var allocatorTotalChunks = 0
|
||||
while allocatorSlab != 0 {
|
||||
let allocations = swift_reflection_asyncTaskSlabAllocations(context,
|
||||
allocatorSlab)
|
||||
guard allocations.Error == nil else { break }
|
||||
allocatorTotalSize += Int(allocations.SlabSize)
|
||||
allocatorTotalChunks += Int(allocations.ChunkCount)
|
||||
|
||||
allocatorSlab = allocations.NextSlab
|
||||
}
|
||||
|
||||
return TaskInfo(
|
||||
address: task,
|
||||
jobFlags: reflectionInfo.JobFlags,
|
||||
taskStatusFlags: reflectionInfo.TaskStatusFlags,
|
||||
id: reflectionInfo.Id,
|
||||
runJob: reflectionInfo.RunJob,
|
||||
allocatorSlabPtr: reflectionInfo.AllocatorSlabPtr,
|
||||
allocatorTotalSize: allocatorTotalSize,
|
||||
allocatorTotalChunks: allocatorTotalChunks,
|
||||
childTasks: children,
|
||||
asyncBacktrace: asyncBacktraceFrames
|
||||
)
|
||||
}
|
||||
|
||||
func taskHierarchy() -> [(level: Int, lastChild: Bool, task: TaskInfo)] {
|
||||
var hierarchy: [(level: Int, lastChild: Bool, task: TaskInfo)] = []
|
||||
|
||||
let topLevelTasks = tasks.values.filter{ $0.parent == nil }
|
||||
for top in topLevelTasks.sorted(by: { $0.id < $1.id }) {
|
||||
var stack: [(index: Int, task: TaskInfo)] = [(0, top)]
|
||||
hierarchy.append((0, true, top))
|
||||
|
||||
while let (index, task) = stack.popLast() {
|
||||
if index < task.childTasks.count {
|
||||
stack.append((index + 1, task))
|
||||
let childPtr = task.childTasks[index]
|
||||
let childTask = tasks[childPtr]!
|
||||
hierarchy.append((stack.count, index == task.childTasks.count - 1, childTask))
|
||||
stack.append((0, childTask))
|
||||
}
|
||||
}
|
||||
}
|
||||
return hierarchy
|
||||
}
|
||||
|
||||
func remove(from: String, upTo: String) -> String {
|
||||
from.withCString {
|
||||
if let found = strstr($0, upTo) {
|
||||
return String(cString: found + strlen(upTo))
|
||||
}
|
||||
return from
|
||||
}
|
||||
}
|
||||
|
||||
func symbolicateBacktracePointer(ptr: swift_reflection_ptr_t) -> String {
|
||||
guard let name = inspector.getSymbol(address: ptr).name else {
|
||||
return "<\(hex: ptr)>"
|
||||
}
|
||||
|
||||
return remove(from: name, upTo: " resume partial function for ")
|
||||
}
|
||||
|
||||
func flagsStrings<T: BinaryInteger>(flags: T, strings: [T: String]) -> [String] {
|
||||
return strings.sorted{ $0.key < $1.key }
|
||||
.filter({ ($0.key & flags) != 0})
|
||||
.map{ $0.value }
|
||||
}
|
||||
|
||||
func flagsString<T: BinaryInteger>(flags: T, strings: [T: String]) -> String {
|
||||
let flagStrs = flagsStrings(flags: flags, strings: strings)
|
||||
if flagStrs.isEmpty {
|
||||
return "0"
|
||||
}
|
||||
|
||||
let flagsStr = flagStrs.joined(separator: "|")
|
||||
return flagsStr
|
||||
}
|
||||
|
||||
func decodeTaskFlags(_ info: TaskInfo) -> (
|
||||
priority: UInt32,
|
||||
flags: String
|
||||
) {
|
||||
let priority = (info.jobFlags >> 8) & 0xff
|
||||
let jobFlags = flagsStrings(flags: info.jobFlags, strings: [
|
||||
1 << 24: "childTask",
|
||||
1 << 25: "future",
|
||||
1 << 26: "groupChildTask",
|
||||
1 << 28: "asyncLetTask"
|
||||
])
|
||||
let taskFlags = flagsStrings(flags: info.taskStatusFlags, strings: [
|
||||
0x100: "cancelled",
|
||||
0x200: "locked",
|
||||
0x400: "escalated",
|
||||
0x800: "running"
|
||||
])
|
||||
let allFlags = jobFlags + taskFlags
|
||||
let flagsStr = allFlags.isEmpty ? "0" : allFlags.joined(separator: "|")
|
||||
return (priority, flagsStr)
|
||||
}
|
||||
|
||||
func decodeActorFlags(_ flags: UInt64) -> (
|
||||
status: String,
|
||||
flags: String,
|
||||
maxPriority: UInt64
|
||||
) {
|
||||
let statuses: [UInt64: String] = [
|
||||
0: "idle",
|
||||
1: "scheduled",
|
||||
2: "running",
|
||||
3: "zombie-latching",
|
||||
4: "zombie-ready-for-deallocation"
|
||||
]
|
||||
let flagsString = flagsString(flags: flags, strings: [
|
||||
1 << 3: "hasActiveInlineJob",
|
||||
1 << 4: "isDistributedRemote"
|
||||
])
|
||||
|
||||
let status = flags & 0x7
|
||||
let maxPriority = (flags >> 8) & 0xff
|
||||
return (
|
||||
status: statuses[status] ?? "unknown(\(status))",
|
||||
flags: flagsString,
|
||||
maxPriority: maxPriority
|
||||
)
|
||||
}
|
||||
|
||||
func dumpTasks() {
|
||||
print("TASKS")
|
||||
|
||||
var lastChilds: [Bool] = []
|
||||
|
||||
let hierarchy = taskHierarchy()
|
||||
for (i, (level, lastChild, task)) in hierarchy.enumerated() {
|
||||
lastChilds.removeSubrange(level...)
|
||||
lastChilds.append(lastChild)
|
||||
|
||||
let nextEntry = i < hierarchy.count - 1 ? hierarchy[i + 1] : nil
|
||||
let prevEntry = i > 0 ? hierarchy[i - 1] : nil
|
||||
|
||||
let levelWillDecrease = level > (nextEntry?.level ?? -1)
|
||||
let levelDidIncrease = level > (prevEntry?.level ?? -1)
|
||||
|
||||
var prefix = ""
|
||||
for lastChild in lastChilds {
|
||||
prefix += lastChild ? " " : " | "
|
||||
}
|
||||
prefix += " "
|
||||
let firstPrefix = String(prefix.dropLast(5) + (
|
||||
level == 0 ? " " :
|
||||
lastChild ? "`--" :
|
||||
"+--"))
|
||||
if levelDidIncrease {
|
||||
print(prefix)
|
||||
}
|
||||
|
||||
var firstLine = true
|
||||
func output(_ str: String) {
|
||||
print((firstLine ? firstPrefix : prefix) + str)
|
||||
firstLine = false
|
||||
}
|
||||
|
||||
let runJobSymbol = inspector.getSymbol(address: task.runJob)
|
||||
let runJobLibrary = runJobSymbol.library ?? "<unknown>"
|
||||
|
||||
let symbolicatedBacktrace = task.asyncBacktrace.map(symbolicateBacktracePointer)
|
||||
|
||||
let flags = decodeTaskFlags(task)
|
||||
|
||||
output("Task \(hex: task.id) - flags=\(flags.flags) priority=\(hex: flags.priority) address=\(hex: task.address)")
|
||||
if let parent = task.parent {
|
||||
output("parent: \(hex: parent)")
|
||||
}
|
||||
|
||||
if let first = symbolicatedBacktrace.first {
|
||||
output("async backtrace: \(first)")
|
||||
for entry in symbolicatedBacktrace.dropFirst() {
|
||||
output(" \(entry)")
|
||||
}
|
||||
}
|
||||
|
||||
output("resume function: \(symbolicateBacktracePointer(ptr: task.runJob)) in \(runJobLibrary)")
|
||||
output("task allocator: \(task.allocatorTotalSize) bytes in \(task.allocatorTotalChunks) chunks")
|
||||
|
||||
if task.childTasks.count > 0 {
|
||||
let s = task.childTasks.count > 1 ? "s" : ""
|
||||
output("* \(task.childTasks.count) child task\(s)")
|
||||
}
|
||||
|
||||
if (task.childTasks.isEmpty) && i < hierarchy.count - 1 {
|
||||
print(prefix)
|
||||
}
|
||||
}
|
||||
|
||||
print("")
|
||||
}
|
||||
|
||||
func dumpActors() {
|
||||
print("ACTORS")
|
||||
|
||||
for actor in actors {
|
||||
let metadata = swift_reflection_metadataForObject(context, UInt(actor))
|
||||
let metadataName = name(metadata: swift_reflection_ptr_t(metadata)) ?? "<unknown class name>"
|
||||
let info = swift_reflection_actorInfo(context, actor);
|
||||
|
||||
let flags = decodeActorFlags(info.Flags)
|
||||
|
||||
print(" \(hex: actor) \(metadataName) status=\(flags.status) flags=\(flags.flags) maxPriority=\(hex: flags.maxPriority)")
|
||||
|
||||
func jobStr(_ job: swift_reflection_ptr_t) -> String {
|
||||
if let task = tasks[job] {
|
||||
return "Task \(hex: task.id) \(symbolicateBacktracePointer(ptr: task.runJob))"
|
||||
}
|
||||
return "<internal job \(hex: job)>"
|
||||
}
|
||||
|
||||
var job = info.FirstJob
|
||||
if job == 0 {
|
||||
print(" no jobs queued")
|
||||
} else {
|
||||
print(" job queue: \(jobStr(job))")
|
||||
while job != 0 {
|
||||
job = swift_reflection_nextJob(context, job);
|
||||
if job != 0 {
|
||||
print(" \(jobStr(job))")
|
||||
}
|
||||
}
|
||||
}
|
||||
print("")
|
||||
}
|
||||
}
|
||||
|
||||
func dumpThreads() {
|
||||
print("THREADS")
|
||||
if threadCurrentTasks.isEmpty {
|
||||
print(" no threads with active tasks")
|
||||
return
|
||||
}
|
||||
|
||||
for (thread, task) in threadCurrentTasks {
|
||||
let taskStr: String
|
||||
if let info = tasks[task] {
|
||||
taskStr = "\(hex: info.id)"
|
||||
} else {
|
||||
taskStr = "<unknown task \(hex: task)>"
|
||||
}
|
||||
print(" Thread \(hex: thread) - current task: \(taskStr)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ class Inspector {
|
||||
let task: task_t
|
||||
let symbolicator: CSTypeRef
|
||||
let swiftCore: CSTypeRef
|
||||
let swiftConcurrency: CSTypeRef
|
||||
|
||||
init?(pid: pid_t) {
|
||||
task = Self.findTask(pid, tryForkCorpse: false)
|
||||
@@ -26,6 +27,8 @@ class Inspector {
|
||||
symbolicator = CSSymbolicatorCreateWithTask(task)
|
||||
swiftCore = CSSymbolicatorGetSymbolOwnerWithNameAtTime(
|
||||
symbolicator, "libswiftCore.dylib", kCSNow)
|
||||
swiftConcurrency = CSSymbolicatorGetSymbolOwnerWithNameAtTime(
|
||||
symbolicator, "libswift_Concurrency.dylib", kCSNow)
|
||||
_ = task_start_peeking(task)
|
||||
}
|
||||
|
||||
@@ -74,8 +77,11 @@ class Inspector {
|
||||
}
|
||||
|
||||
func getAddr(symbolName: String) -> swift_addr_t {
|
||||
let symbol = CSSymbolOwnerGetSymbolWithMangledName(swiftCore,
|
||||
"_" + symbolName)
|
||||
let fullName = "_" + symbolName
|
||||
var symbol = CSSymbolOwnerGetSymbolWithMangledName(swiftCore, fullName)
|
||||
if CSIsNull(symbol) {
|
||||
symbol = CSSymbolOwnerGetSymbolWithMangledName(swiftConcurrency, fullName)
|
||||
}
|
||||
let range = CSSymbolGetRange(symbol)
|
||||
return swift_addr_t(range.location)
|
||||
}
|
||||
@@ -106,6 +112,57 @@ class Inspector {
|
||||
return task_peek(task, address, mach_vm_size_t(size))
|
||||
}
|
||||
|
||||
func threadCurrentTasks() -> [(threadID: UInt64, currentTask: swift_addr_t)] {
|
||||
var threadList: UnsafeMutablePointer<thread_t>? = nil
|
||||
var threadCount: mach_msg_type_number_t = 0
|
||||
|
||||
var kr = task_threads(task, &threadList, &threadCount)
|
||||
if kr != KERN_SUCCESS {
|
||||
print("Unable to gather threads of remote process: \(machErrStr(kr))")
|
||||
return []
|
||||
}
|
||||
|
||||
defer {
|
||||
// Deallocate the port rights for the threads.
|
||||
for i in 0..<threadCount {
|
||||
mach_port_deallocate(mach_task_self_, threadList![Int(i)]);
|
||||
}
|
||||
|
||||
// Deallocate the thread list.
|
||||
let ptr = vm_address_t(bitPattern: threadList)
|
||||
let size = vm_size_t(MemoryLayout<thread_t>.size) * vm_size_t(threadCount)
|
||||
vm_deallocate(mach_task_self_, ptr, size);
|
||||
}
|
||||
|
||||
var results: [(threadID: UInt64, currentTask: swift_addr_t)] = []
|
||||
for i in 0..<threadCount {
|
||||
let THREAD_IDENTIFIER_INFO_COUNT = MemoryLayout<thread_identifier_info_data_t>.size / MemoryLayout<natural_t>.size
|
||||
var info = thread_identifier_info_data_t()
|
||||
var infoCount = mach_msg_type_number_t(THREAD_IDENTIFIER_INFO_COUNT)
|
||||
withUnsafeMutablePointer(to: &info) {
|
||||
$0.withMemoryRebound(to: integer_t.self, capacity: THREAD_IDENTIFIER_INFO_COUNT) {
|
||||
kr = thread_info(threadList![Int(i)],
|
||||
thread_flavor_t(THREAD_IDENTIFIER_INFO), $0,
|
||||
&infoCount)
|
||||
}
|
||||
}
|
||||
if (kr != KERN_SUCCESS) {
|
||||
print("Unable to get info for thread \(i): \(machErrStr(kr))")
|
||||
} else {
|
||||
let tlsStart = info.thread_handle
|
||||
if tlsStart != 0 {
|
||||
let SWIFT_CONCURRENCY_TASK_KEY = 103
|
||||
let currentTaskPointer = tlsStart + UInt64(SWIFT_CONCURRENCY_TASK_KEY * MemoryLayout<UnsafeRawPointer>.size)
|
||||
if let ptr = read(address: currentTaskPointer, size: MemoryLayout<UnsafeRawPointer>.size) {
|
||||
let currentTask = ptr.load(as: UInt.self)
|
||||
results.append((threadID: info.thread_id, currentTask: swift_addr_t(currentTask)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
enum Callbacks {
|
||||
static let QueryDataLayout: @convention(c)
|
||||
(UnsafeMutableRawPointer?,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import SwiftRemoteMirror
|
||||
|
||||
extension DefaultStringInterpolation {
|
||||
mutating func appendInterpolation(hex: swift_reflection_ptr_t) {
|
||||
mutating func appendInterpolation<T>(hex: T) where T: BinaryInteger {
|
||||
appendInterpolation("0x")
|
||||
appendInterpolation(String(hex, radix: 16))
|
||||
}
|
||||
|
||||
@@ -169,6 +169,7 @@ struct SwiftInspect: ParsableCommand {
|
||||
DumpGenericMetadata.self,
|
||||
DumpCacheNodes.self,
|
||||
DumpArrays.self,
|
||||
DumpConcurrency.self,
|
||||
])
|
||||
}
|
||||
|
||||
@@ -260,7 +261,7 @@ struct DumpCacheNodes: ParsableCommand {
|
||||
|
||||
struct DumpArrays: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(
|
||||
abstract: "Print the target's metadata cache nodes.")
|
||||
abstract: "Print information about array objects in the target.")
|
||||
|
||||
@OptionGroup()
|
||||
var options: UniversalOptions
|
||||
@@ -272,4 +273,18 @@ struct DumpArrays: ParsableCommand {
|
||||
}
|
||||
}
|
||||
|
||||
struct DumpConcurrency: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(
|
||||
abstract: "Print information about the target's concurrency runtime.")
|
||||
|
||||
@OptionGroup()
|
||||
var options: UniversalOptions
|
||||
|
||||
func run() throws {
|
||||
try withReflectionContext(nameOrPid: options.nameOrPid) {
|
||||
try dumpConcurrency(context: $0, inspector: $1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SwiftInspect.main()
|
||||
|
||||
@@ -58,6 +58,13 @@ enum Sym {
|
||||
static let CSSymbolicatorGetSymbolWithAddressAtTime: @convention(c)
|
||||
(CSSymbolicatorRef, mach_vm_address_t, CSMachineTime) -> CSSymbolRef =
|
||||
symbol(coreSymbolicationHandle, "CSSymbolicatorGetSymbolWithAddressAtTime")
|
||||
static let CSSymbolicatorForeachSymbolOwnerAtTime:
|
||||
@convention(c) (CSSymbolicatorRef, CSMachineTime, @convention(block) (CSSymbolOwnerRef) -> Void) -> UInt =
|
||||
symbol(coreSymbolicationHandle, "CSSymbolicatorForeachSymbolOwnerAtTime")
|
||||
static let CSSymbolOwnerGetBaseAddress: @convention(c) (CSSymbolOwnerRef) -> mach_vm_address_t =
|
||||
symbol(symbolicationHandle, "CSSymbolOwnerGetBaseAddress")
|
||||
static let CSIsNull: @convention(c) (CSTypeRef) -> CBool =
|
||||
symbol(coreSymbolicationHandle, "CSIsNull")
|
||||
static let task_start_peeking: @convention(c) (task_t) -> kern_return_t =
|
||||
symbol(symbolicationHandle, "task_start_peeking")
|
||||
static let task_peek: @convention(c) (task_t, mach_vm_address_t, mach_vm_size_t,
|
||||
@@ -77,13 +84,6 @@ enum Sym {
|
||||
@convention(c) (task_t, UnsafeMutableRawPointer?, CUnsignedInt, vm_range_recorder_t)
|
||||
-> Void =
|
||||
symbol(symbolicationHandle, "task_enumerate_malloc_blocks")
|
||||
|
||||
static let CSSymbolicatorForeachSymbolOwnerAtTime:
|
||||
@convention(c) (CSSymbolicatorRef, CSMachineTime, @convention(block) (CSSymbolOwnerRef) -> Void) -> UInt =
|
||||
symbol(coreSymbolicationHandle, "CSSymbolicatorForeachSymbolOwnerAtTime")
|
||||
|
||||
static let CSSymbolOwnerGetBaseAddress: @convention(c) (CSSymbolOwnerRef) -> mach_vm_address_t =
|
||||
symbol(symbolicationHandle, "CSSymbolOwnerGetBaseAddress")
|
||||
}
|
||||
|
||||
typealias CSMachineTime = UInt64
|
||||
@@ -173,6 +173,10 @@ func CSSymbolOwnerGetBaseAddress(_ symbolOwner: CSSymbolOwnerRef) -> mach_vm_add
|
||||
return Sym.CSSymbolOwnerGetBaseAddress(symbolOwner)
|
||||
}
|
||||
|
||||
func CSIsNull(_ symbol: CSTypeRef) -> Bool {
|
||||
Sym.CSIsNull(symbol)
|
||||
}
|
||||
|
||||
func task_start_peeking(_ task: task_t) -> Bool {
|
||||
let result = Sym.task_start_peeking(task)
|
||||
if result == KERN_SUCCESS {
|
||||
@@ -189,7 +193,6 @@ func task_peek(
|
||||
var ptr: UnsafeRawPointer? = nil
|
||||
let result = Sym.task_peek(task, start, size, &ptr)
|
||||
if result != KERN_SUCCESS {
|
||||
print("Unable to read (\(start), \(size)): \(machErrStr(result))", to: &Std.err)
|
||||
return nil
|
||||
}
|
||||
return ptr
|
||||
|
||||
Reference in New Issue
Block a user