Skip to content

Commit a82ea12

Browse files
committed
[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
1 parent f5e5a07 commit a82ea12

File tree

15 files changed

+962
-42
lines changed

15 files changed

+962
-42
lines changed

include/swift/Reflection/ReflectionContext.h

Lines changed: 219 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ class ReflectionContext
101101
std::vector<MemoryReader::ReadBytesResult> savedBuffers;
102102
std::vector<std::tuple<RemoteAddress, RemoteAddress>> imageRanges;
103103

104+
bool setupTargetPointers = false;
105+
typename super::StoredPointer target_non_future_adapter = 0;
106+
typename super::StoredPointer target_future_adapter = 0;
107+
typename super::StoredPointer target_task_wait_throwing_resume_adapter = 0;
108+
typename super::StoredPointer target_task_future_wait_resume_adapter = 0;
109+
104110
public:
105111
using super::getBuilder;
106112
using super::readDemanglingForContextDescriptor;
@@ -137,6 +143,21 @@ class ReflectionContext
137143
std::vector<AsyncTaskAllocationChunk> Chunks;
138144
};
139145

146+
struct AsyncTaskInfo {
147+
uint32_t JobFlags;
148+
uint64_t TaskStatusFlags;
149+
uint64_t Id;
150+
StoredPointer RunJob;
151+
StoredPointer AllocatorSlabPtr;
152+
std::vector<StoredPointer> ChildTasks;
153+
std::vector<StoredPointer> AsyncBacktraceFrames;
154+
};
155+
156+
struct ActorInfo {
157+
StoredSize Flags;
158+
StoredPointer FirstJob;
159+
};
160+
140161
explicit ReflectionContext(std::shared_ptr<MemoryReader> reader)
141162
: super(std::move(reader), *this)
142163
{}
@@ -1067,6 +1088,31 @@ class ReflectionContext
10671088
return dyn_cast_or_null<const RecordTypeInfo>(TypeInfo);
10681089
}
10691090

1091+
bool metadataIsActor(StoredPointer MetadataAddress) {
1092+
auto Metadata = readMetadata(MetadataAddress);
1093+
if (!Metadata)
1094+
return false;
1095+
1096+
// Only classes can be actors.
1097+
if (Metadata->getKind() != MetadataKind::Class)
1098+
return false;
1099+
1100+
auto DescriptorAddress =
1101+
super::readAddressOfNominalTypeDescriptor(Metadata);
1102+
if (!DescriptorAddress)
1103+
return false;
1104+
1105+
auto DescriptorBytes =
1106+
getReader().readBytes(RemoteAddress(DescriptorAddress),
1107+
sizeof(TargetTypeContextDescriptor<Runtime>));
1108+
if (!DescriptorBytes)
1109+
return false;
1110+
auto Descriptor =
1111+
reinterpret_cast<const TargetTypeContextDescriptor<Runtime> *>(
1112+
DescriptorBytes.get());
1113+
return Descriptor->getTypeContextDescriptorFlags().class_isActor();
1114+
}
1115+
10701116
/// Iterate the protocol conformance cache tree rooted at NodePtr, calling
10711117
/// Call with the type and protocol in each node.
10721118
void iterateConformanceTree(StoredPointer NodePtr,
@@ -1378,22 +1424,179 @@ class ReflectionContext
13781424
return {llvm::None, {Slab->Next, SlabSize, {Chunk}}};
13791425
}
13801426

1381-
std::pair<llvm::Optional<std::string>, StoredPointer>
1382-
asyncTaskSlabPtr(StoredPointer AsyncTaskPtr) {
1383-
using AsyncTask = AsyncTask<Runtime>;
1384-
1385-
auto AsyncTaskBytes =
1386-
getReader().readBytes(RemoteAddress(AsyncTaskPtr), sizeof(AsyncTask));
1387-
auto *AsyncTaskObj =
1388-
reinterpret_cast<const AsyncTask *>(AsyncTaskBytes.get());
1427+
std::pair<llvm::Optional<std::string>, AsyncTaskInfo>
1428+
asyncTaskInfo(StoredPointer AsyncTaskPtr) {
1429+
auto AsyncTaskObj = readObj<AsyncTask<Runtime>>(AsyncTaskPtr);
13891430
if (!AsyncTaskObj)
1390-
return {std::string("failure reading async task"), 0};
1431+
return {std::string("failure reading async task"), {}};
1432+
1433+
AsyncTaskInfo Info{};
1434+
Info.JobFlags = AsyncTaskObj->Flags;
1435+
Info.TaskStatusFlags = AsyncTaskObj->PrivateStorage.Status.Flags;
1436+
Info.Id =
1437+
AsyncTaskObj->Id | ((uint64_t)AsyncTaskObj->PrivateStorage.Id << 32);
1438+
Info.AllocatorSlabPtr = AsyncTaskObj->PrivateStorage.Allocator.FirstSlab;
1439+
Info.RunJob = getRunJob(AsyncTaskObj.get());
1440+
1441+
// Find all child tasks.
1442+
auto RecordPtr = AsyncTaskObj->PrivateStorage.Status.Record;
1443+
while (RecordPtr) {
1444+
auto RecordObj = readObj<TaskStatusRecord<Runtime>>(RecordPtr);
1445+
if (!RecordObj)
1446+
break;
1447+
1448+
// This cuts off high bits if our size_t doesn't match the target's. We
1449+
// only read the Kind bits which are at the bottom, so that's OK here.
1450+
// Beware of this when reading anything else.
1451+
TaskStatusRecordFlags Flags{RecordObj->Flags};
1452+
auto Kind = Flags.getKind();
1453+
1454+
StoredPointer ChildTask = 0;
1455+
if (Kind == TaskStatusRecordKind::ChildTask) {
1456+
auto RecordObj = readObj<ChildTaskStatusRecord<Runtime>>(RecordPtr);
1457+
if (RecordObj)
1458+
ChildTask = RecordObj->FirstChild;
1459+
} else if (Kind == TaskStatusRecordKind::TaskGroup) {
1460+
auto RecordObj = readObj<TaskGroupTaskStatusRecord<Runtime>>(RecordPtr);
1461+
if (RecordObj)
1462+
ChildTask = RecordObj->FirstChild;
1463+
}
1464+
1465+
while (ChildTask) {
1466+
Info.ChildTasks.push_back(ChildTask);
1467+
1468+
StoredPointer ChildFragmentAddr =
1469+
ChildTask + sizeof(AsyncTask<Runtime>);
1470+
auto ChildFragmentObj =
1471+
readObj<ChildFragment<Runtime>>(ChildFragmentAddr);
1472+
if (ChildFragmentObj)
1473+
ChildTask = ChildFragmentObj->NextChild;
1474+
else
1475+
ChildTask = 0;
1476+
}
1477+
1478+
RecordPtr = RecordObj->Parent;
1479+
}
1480+
1481+
// Walk the async backtrace if the task isn't running or cancelled.
1482+
// TODO: Use isEnqueued from https://github.com/apple/swift/pull/41088/ once
1483+
// that's available.
1484+
int IsCancelledFlag = 0x100;
1485+
int IsRunningFlag = 0x800;
1486+
if (!(AsyncTaskObj->PrivateStorage.Status.Flags & IsCancelledFlag) &&
1487+
!(AsyncTaskObj->PrivateStorage.Status.Flags & IsRunningFlag)) {
1488+
auto ResumeContext = AsyncTaskObj->ResumeContextAndReserved[0];
1489+
while (ResumeContext) {
1490+
auto ResumeContextObj = readObj<AsyncContext<Runtime>>(ResumeContext);
1491+
if (!ResumeContextObj)
1492+
break;
1493+
Info.AsyncBacktraceFrames.push_back(
1494+
stripSignedPointer(ResumeContextObj->ResumeParent));
1495+
ResumeContext = stripSignedPointer(ResumeContextObj->Parent);
1496+
}
1497+
}
1498+
1499+
return {llvm::None, Info};
1500+
}
1501+
1502+
std::pair<llvm::Optional<std::string>, ActorInfo>
1503+
actorInfo(StoredPointer ActorPtr) {
1504+
using DefaultActorImpl = DefaultActorImpl<Runtime>;
1505+
1506+
auto ActorObj = readObj<DefaultActorImpl>(ActorPtr);
1507+
if (!ActorObj)
1508+
return {std::string("failure reading actor"), {}};
1509+
1510+
ActorInfo Info{};
1511+
Info.Flags = ActorObj->Flags;
1512+
1513+
// Status is the low 3 bits of Flags. Status of 0 is Idle. Don't read
1514+
// FirstJob when idle.
1515+
auto Status = Info.Flags & 0x7;
1516+
if (Status != 0) {
1517+
// This is a JobRef which stores flags in the low bits.
1518+
Info.FirstJob = ActorObj->FirstJob & ~StoredPointer(0x3);
1519+
}
1520+
return {llvm::None, Info};
1521+
}
13911522

1392-
StoredPointer SlabPtr = AsyncTaskObj->PrivateStorage.Allocator.FirstSlab;
1393-
return {llvm::None, SlabPtr};
1523+
StoredPointer nextJob(StoredPointer JobPtr) {
1524+
using Job = Job<Runtime>;
1525+
1526+
auto JobBytes = getReader().readBytes(RemoteAddress(JobPtr), sizeof(Job));
1527+
auto *JobObj = reinterpret_cast<const Job *>(JobBytes.get());
1528+
if (!JobObj)
1529+
return 0;
1530+
1531+
// This is a JobRef which stores flags in the low bits.
1532+
return JobObj->SchedulerPrivate[0] & ~StoredPointer(0x3);
13941533
}
13951534

13961535
private:
1536+
// Get the most human meaningful "run job" function pointer from the task,
1537+
// like AsyncTask::getResumeFunctionForLogging does.
1538+
StoredPointer getRunJob(const AsyncTask<Runtime> *AsyncTaskObj) {
1539+
auto Fptr = stripSignedPointer(AsyncTaskObj->RunJob);
1540+
1541+
loadTargetPointers();
1542+
auto ResumeContextPtr = AsyncTaskObj->ResumeContextAndReserved[0];
1543+
if (target_non_future_adapter && Fptr == target_non_future_adapter) {
1544+
using Prefix = AsyncContextPrefix<Runtime>;
1545+
auto PrefixAddr = ResumeContextPtr - sizeof(Prefix);
1546+
auto PrefixBytes =
1547+
getReader().readBytes(RemoteAddress(PrefixAddr), sizeof(Prefix));
1548+
if (PrefixBytes) {
1549+
auto PrefixPtr = reinterpret_cast<const Prefix *>(PrefixBytes.get());
1550+
return stripSignedPointer(PrefixPtr->AsyncEntryPoint);
1551+
}
1552+
} else if (target_future_adapter && Fptr == target_future_adapter) {
1553+
using Prefix = FutureAsyncContextPrefix<Runtime>;
1554+
auto PrefixAddr = ResumeContextPtr - sizeof(Prefix);
1555+
auto PrefixBytes =
1556+
getReader().readBytes(RemoteAddress(PrefixAddr), sizeof(Prefix));
1557+
if (PrefixBytes) {
1558+
auto PrefixPtr = reinterpret_cast<const Prefix *>(PrefixBytes.get());
1559+
return stripSignedPointer(PrefixPtr->AsyncEntryPoint);
1560+
}
1561+
} else if ((target_task_wait_throwing_resume_adapter &&
1562+
Fptr == target_task_wait_throwing_resume_adapter) ||
1563+
(target_task_future_wait_resume_adapter &&
1564+
Fptr == target_task_future_wait_resume_adapter)) {
1565+
auto ContextBytes = getReader().readBytes(RemoteAddress(ResumeContextPtr),
1566+
sizeof(AsyncContext<Runtime>));
1567+
if (ContextBytes) {
1568+
auto ContextPtr =
1569+
reinterpret_cast<const AsyncContext<Runtime> *>(ContextBytes.get());
1570+
return stripSignedPointer(ContextPtr->ResumeParent);
1571+
}
1572+
}
1573+
1574+
return Fptr;
1575+
}
1576+
1577+
void loadTargetPointers() {
1578+
if (setupTargetPointers)
1579+
return;
1580+
1581+
auto getFunc = [&](const std::string &name) -> StoredPointer {
1582+
auto Symbol = getReader().getSymbolAddress(name);
1583+
if (!Symbol)
1584+
return 0;
1585+
auto Pointer = getReader().readPointer(Symbol, sizeof(StoredPointer));
1586+
if (!Pointer)
1587+
return 0;
1588+
return Pointer->getResolvedAddress().getAddressData();
1589+
};
1590+
target_non_future_adapter =
1591+
getFunc("_swift_concurrency_debug_non_future_adapter");
1592+
target_future_adapter = getFunc("_swift_concurrency_debug_future_adapter");
1593+
target_task_wait_throwing_resume_adapter =
1594+
getFunc("_swift_concurrency_debug_task_wait_throwing_resume_adapter");
1595+
target_task_future_wait_resume_adapter =
1596+
getFunc("_swift_concurrency_debug_task_future_wait_resume_adapter");
1597+
setupTargetPointers = true;
1598+
}
1599+
13971600
const TypeInfo *
13981601
getClosureContextInfo(StoredPointer Context, const ClosureContextInfo &Info,
13991602
remote::TypeInfoProvider *ExternalTypeInfo) {
@@ -1615,6 +1818,11 @@ class ReflectionContext
16151818

16161819
return llvm::None;
16171820
}
1821+
1822+
template <typename T>
1823+
MemoryReader::ReadObjResult<T> readObj(StoredPointer Ptr) {
1824+
return getReader().template readObj<T>(RemoteAddress(Ptr));
1825+
}
16181826
};
16191827

16201828
} // end namespace reflection

include/swift/Reflection/RuntimeInternals.h

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ struct Job {
7575
uint32_t Flags;
7676
uint32_t Id;
7777
typename Runtime::StoredPointer Reserved[2];
78-
typename Runtime::StoredPointer RunJob;
78+
typename Runtime::StoredSignedPointer RunJob;
7979
};
8080

8181
template <typename Runtime>
@@ -104,6 +104,8 @@ struct AsyncTaskPrivateStorage {
104104
ActiveTaskStatus<Runtime> Status;
105105
StackAllocator<Runtime> Allocator;
106106
typename Runtime::StoredPointer Local;
107+
typename Runtime::StoredPointer ExclusivityAccessSet[2];
108+
uint32_t Id;
107109
};
108110

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

115-
AsyncTaskPrivateStorage<Runtime> PrivateStorage;
117+
union {
118+
AsyncTaskPrivateStorage<Runtime> PrivateStorage;
119+
typename Runtime::StoredPointer PrivateStorageRaw[14];
120+
};
121+
};
122+
123+
template <typename Runtime>
124+
struct AsyncContext {
125+
typename Runtime::StoredSignedPointer Parent;
126+
typename Runtime::StoredSignedPointer ResumeParent;
127+
uint32_t Flags;
128+
};
129+
130+
template <typename Runtime>
131+
struct AsyncContextPrefix {
132+
typename Runtime::StoredSignedPointer AsyncEntryPoint;
133+
typename Runtime::StoredPointer ClosureContext;
134+
typename Runtime::StoredPointer ErrorResult;
135+
};
136+
137+
template <typename Runtime>
138+
struct FutureAsyncContextPrefix {
139+
typename Runtime::StoredPointer IndirectResult;
140+
typename Runtime::StoredSignedPointer AsyncEntryPoint;
141+
typename Runtime::StoredPointer ClosureContext;
142+
typename Runtime::StoredPointer ErrorResult;
143+
};
144+
145+
template <typename Runtime>
146+
struct DefaultActorImpl {
147+
HeapObject<Runtime> HeapObject;
148+
typename Runtime::StoredPointer FirstJob;
149+
typename Runtime::StoredSize Flags;
150+
};
151+
152+
template <typename Runtime>
153+
struct TaskStatusRecord {
154+
typename Runtime::StoredSize Flags;
155+
typename Runtime::StoredPointer Parent;
156+
};
157+
158+
template <typename Runtime>
159+
struct ChildTaskStatusRecord : TaskStatusRecord<Runtime> {
160+
typename Runtime::StoredPointer FirstChild;
161+
};
162+
163+
template <typename Runtime>
164+
struct TaskGroupTaskStatusRecord : TaskStatusRecord<Runtime> {
165+
typename Runtime::StoredPointer FirstChild;
166+
};
167+
168+
template <typename Runtime>
169+
struct ChildFragment {
170+
typename Runtime::StoredPointer Parent;
171+
typename Runtime::StoredPointer NextChild;
116172
};
117173

118174
} // end namespace reflection

include/swift/Remote/MemoryReader.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ class MemoryReader {
4040
using ReadBytesResult =
4141
std::unique_ptr<const void, std::function<void(const void *)>>;
4242

43+
template <typename T>
44+
using ReadObjResult =
45+
std::unique_ptr<const T, std::function<void(const void *)>>;
46+
4347
virtual bool queryDataLayout(DataLayoutQueryType type, void *inBuffer,
4448
void *outBuffer) = 0;
4549

@@ -90,6 +94,15 @@ class MemoryReader {
9094
return true;
9195
}
9296

97+
template <typename T>
98+
ReadObjResult<T> readObj(RemoteAddress address) {
99+
auto bytes = readBytes(address, sizeof(T));
100+
auto deleter = bytes.get_deleter();
101+
auto ptr = bytes.get();
102+
bytes.release();
103+
return ReadObjResult<T>(reinterpret_cast<const T *>(ptr), deleter);
104+
}
105+
93106
/// Attempts to read 'size' bytes from the given address in the remote process.
94107
///
95108
/// Returns a pointer to the requested data and a function that must be called to

0 commit comments

Comments
 (0)