[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:
Mike Ash
2022-01-05 15:20:05 -05:00
parent f5e5a07230
commit a82ea120a4
15 changed files with 962 additions and 42 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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 *>(

View File

@@ -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()

View File

@@ -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);
}

View File

@@ -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_ \

View 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)")
}
}
}

View File

@@ -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?,

View File

@@ -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))
}

View File

@@ -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()

View File

@@ -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