Skip to content

Commit fec8d72

Browse files
authored
Debugger hook/breakpoint for issue reporting from the Swift runtime (#10293)
* Implements a debugger hook (breakpoint) API and data structure. This structure is passed to the debugger and describes extra information about a fatal error or a non-fatal warning, which should be logged as a runtime issue. This debugger hook is then used from two places, which currently only log to stderr: - Runtime exclusivity violations. - Swift 3 implicit Obj-C entrypoints. A subsequent LLDB support will be able to catch these callbacks and show the runtime issues in a better way than just logging them to stderr. When the debugger is not attached, this shouldn't have any effect.
1 parent 1ea4d9f commit fec8d72

File tree

4 files changed

+127
-35
lines changed

4 files changed

+127
-35
lines changed

include/swift/Runtime/Debug.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,48 @@ void dumpStackTraceEntry(unsigned index, void *framePC,
134134
LLVM_ATTRIBUTE_NOINLINE
135135
void printCurrentBacktrace(unsigned framesToSkip = 1);
136136

137+
/// Debugger breakpoint ABI. This structure is passed to the debugger (and needs
138+
/// to be stable) and describes extra information about a fatal error or a
139+
/// non-fatal warning, which should be logged as a runtime issue. Please keep
140+
/// all integer values pointer-sized.
141+
struct RuntimeErrorDetails {
142+
// ABI version, needs to be "1" currently.
143+
uintptr_t version;
144+
145+
// A short hyphenated string describing the type of the issue, e.g.
146+
// "precondition-failed" or "exclusivity-violation".
147+
const char *errorType;
148+
149+
// Description of the current thread's stack position.
150+
const char *currentStackDescription;
151+
152+
// Number of frames in the current stack that should be ignored when reporting
153+
// the issue (exluding the reportToDebugger/_swift_runtime_on_report frame).
154+
// The remaining top frame should point to user's code where the bug is.
155+
uintptr_t framesToSkip;
156+
157+
// Address of some associated object (if there's any).
158+
void *memoryAddress;
159+
160+
// A structure describing an extra thread (and its stack) that is related.
161+
struct Thread {
162+
const char *description;
163+
uint64_t threadID;
164+
uintptr_t numFrames;
165+
void **frames;
166+
};
167+
168+
// Number of extra threads (excluding the current thread) that are related,
169+
// and the pointer to the array of extra threads.
170+
uintptr_t numExtraThreads;
171+
Thread *threads;
172+
};
173+
174+
/// Debugger hook. Calling this stops the debugger with a message and details
175+
/// about the issues.
176+
void reportToDebugger(bool isFatal, const char *message,
177+
RuntimeErrorDetails *details = nullptr);
178+
137179
// namespace swift
138180
}
139181

stdlib/public/runtime/Errors.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,20 @@ reportNow(uint32_t flags, const char *message)
243243
#endif
244244
}
245245

246+
LLVM_ATTRIBUTE_NOINLINE SWIFT_RUNTIME_EXPORT
247+
void _swift_runtime_on_report(bool isFatal, const char *message,
248+
RuntimeErrorDetails *details) {
249+
// Do nothing. This function is meant to be used by the debugger.
250+
251+
// The following is necessary to avoid calls from being optimized out.
252+
asm volatile("" ::: "memory");
253+
}
254+
255+
void swift::reportToDebugger(bool isFatal, const char *message,
256+
RuntimeErrorDetails *details) {
257+
_swift_runtime_on_report(isFatal, message, details);
258+
}
259+
246260
/// Report a fatal error to system console, stderr, and crash logs.
247261
/// Does not crash by itself.
248262
void swift::swift_reportError(uint32_t flags,

stdlib/public/runtime/Exclusivity.cpp

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,6 @@ static const char *getAccessName(ExclusivityFlags flags) {
6464
}
6565
}
6666

67-
LLVM_ATTRIBUTE_ALWAYS_INLINE
68-
static void printConflictDetails(const char *oldAccessName, void *oldPC,
69-
const char *newAccessName, void *newPC) {
70-
fprintf(stderr, "Previous access (a %s) started at ", oldAccessName);
71-
if (oldPC) {
72-
dumpStackTraceEntry(0, oldPC, /*shortOutput=*/true);
73-
fprintf(stderr, " (0x%lx).\n", (uintptr_t)oldPC);
74-
} else {
75-
fprintf(stderr, "<unknown>.\n");
76-
}
77-
78-
fprintf(stderr, "Current access (a %s) started at:\n", newAccessName);
79-
// The top frame is in swift_beginAccess, don't print it.
80-
constexpr unsigned framesToSkip = 2;
81-
printCurrentBacktrace(framesToSkip);
82-
}
83-
8467
LLVM_ATTRIBUTE_ALWAYS_INLINE
8568
static void reportExclusivityConflict(ExclusivityFlags oldAction, void *oldPC,
8669
ExclusivityFlags newFlags, void *newPC,
@@ -94,14 +77,53 @@ static void reportExclusivityConflict(ExclusivityFlags oldAction, void *oldPC,
9477
return;
9578
}
9679

97-
fprintf(stderr,
98-
"Simultaneous accesses to 0x%lx, but modification requires exclusive "
99-
"access.\n",
100-
(uintptr_t)pointer);
101-
printConflictDetails(getAccessName(oldAction), oldPC,
102-
getAccessName(getAccessAction(newFlags)), newPC);
80+
constexpr unsigned maxMessageLength = 100;
81+
constexpr unsigned maxAccessDescriptionLength = 50;
82+
char message[maxMessageLength];
83+
snprintf(message, sizeof(message),
84+
"Simultaneous accesses to 0x%lx, but modification requires "
85+
"exclusive access",
86+
(uintptr_t)pointer);
87+
fprintf(stderr, "%s.\n", message);
88+
89+
char oldAccess[maxAccessDescriptionLength];
90+
snprintf(oldAccess, sizeof(oldAccess),
91+
"Previous access (a %s) started at", getAccessName(oldAction));
92+
fprintf(stderr, "%s ", oldAccess);
93+
if (oldPC) {
94+
dumpStackTraceEntry(0, oldPC, /*shortOutput=*/true);
95+
fprintf(stderr, " (0x%lx).\n", (uintptr_t)oldPC);
96+
} else {
97+
fprintf(stderr, "<unknown>.\n");
98+
}
99+
100+
char newAccess[maxAccessDescriptionLength];
101+
snprintf(newAccess, sizeof(newAccess), "Current access (a %s) started at",
102+
getAccessName(getAccessAction(newFlags)));
103+
fprintf(stderr, "%s:\n", newAccess);
104+
// The top frame is in swift_beginAccess, don't print it.
105+
constexpr unsigned framesToSkip = 1;
106+
printCurrentBacktrace(framesToSkip);
107+
108+
bool keepGoing = isWarningOnly(newFlags);
109+
110+
RuntimeErrorDetails::Thread secondaryThread = {
111+
.description = oldAccess,
112+
.numFrames = 1,
113+
.frames = &oldPC
114+
};
115+
RuntimeErrorDetails details = {
116+
.version = 1,
117+
.errorType = "exclusivity-violation",
118+
.currentStackDescription = newAccess,
119+
.framesToSkip = framesToSkip,
120+
.memoryAddress = pointer,
121+
.numExtraThreads = 1,
122+
.threads = &secondaryThread
123+
};
124+
reportToDebugger(!keepGoing, message, &details);
103125

104-
if (isWarningOnly(newFlags)) {
126+
if (keepGoing) {
105127
return;
106128
}
107129

stdlib/public/runtime/SwiftObject.mm

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,17 +1425,31 @@ void swift_objc_swift3ImplicitObjCEntrypoint(id self, SEL selector,
14251425

14261426
if (filenameLength > INT_MAX)
14271427
filenameLength = INT_MAX;
1428-
1429-
reporter(
1430-
flags,
1431-
"*** %*s:%zu:%zu: implicit Objective-C entrypoint %c[%s %s] "
1432-
"is deprecated and will be removed in Swift 4; "
1433-
"add explicit '@objc' to the declaration to emit the Objective-C "
1434-
"entrypoint in Swift 4 and suppress this message\n",
1435-
(int)filenameLength, filename, line, column,
1436-
isInstanceMethod ? '-' : '+',
1437-
class_getName([self class]),
1438-
sel_getName(selector));
1428+
1429+
char *message, *nullTerminatedFilename;
1430+
asprintf(&message,
1431+
"implicit Objective-C entrypoint %c[%s %s] is deprecated and will "
1432+
"be removed in Swift 4",
1433+
isInstanceMethod ? '-' : '+',
1434+
class_getName([self class]),
1435+
sel_getName(selector));
1436+
asprintf(&nullTerminatedFilename, "%*s", (int)filenameLength, filename);
1437+
1438+
RuntimeErrorDetails details = {
1439+
.version = 1,
1440+
.errorType = "implicit-objc-entrypoint",
1441+
.framesToSkip = 1
1442+
};
1443+
bool isFatal = reporter == swift::fatalError;
1444+
reportToDebugger(isFatal, message, &details);
1445+
1446+
reporter(flags,
1447+
"*** %s:%zu:%zu: %s; add explicit '@objc' to the declaration to "
1448+
"emit the Objective-C entrypoint in Swift 4 and suppress this "
1449+
"message\n",
1450+
nullTerminatedFilename, line, column, message);
1451+
free(message);
1452+
free(nullTerminatedFilename);
14391453
}
14401454

14411455
#endif

0 commit comments

Comments
 (0)