-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Debugger hook/breakpoint for issue reporting from the Swift runtime #10293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a045866
363e462
999b28a
1ec98f0
2df5403
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -134,6 +134,48 @@ void dumpStackTraceEntry(unsigned index, void *framePC, | |
LLVM_ATTRIBUTE_NOINLINE | ||
void printCurrentBacktrace(unsigned framesToSkip = 1); | ||
|
||
/// Debugger breakpoint ABI. This structure is passed to the debugger (and needs | ||
/// to be stable) and describes extra information about a fatal error or a | ||
/// non-fatal warning, which should be logged as a runtime issue. Please keep | ||
/// all integer values pointer-sized. | ||
struct RuntimeErrorDetails { | ||
// ABI version, needs to be "1" currently. | ||
uintptr_t version; | ||
|
||
// A short hyphenated string describing the type of the issue, e.g. | ||
// "precondition-failed" or "exclusivity-violation". | ||
const char *errorType; | ||
|
||
// Description of the current thread's stack position. | ||
const char *currentStackDescription; | ||
|
||
// Number of frames in the current stack that should be ignored when reporting | ||
// the issue (exluding the reportToDebugger/_swift_runtime_on_report frame). | ||
// The remaining top frame should point to user's code where the bug is. | ||
uintptr_t framesToSkip; | ||
|
||
// Address of some associated object (if there's any). | ||
void *memoryAddress; | ||
|
||
// A structure describing an extra thread (and its stack) that is related. | ||
struct Thread { | ||
const char *description; | ||
uint64_t threadID; | ||
uintptr_t numFrames; | ||
void **frames; | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you want to describe the # of frames to skip here as well? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it makes sense here: Since the runtime needs to provide the frames itself, it should already strip away any uninteresting frames. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point! |
||
|
||
// Number of extra threads (excluding the current thread) that are related, | ||
// and the pointer to the array of extra threads. | ||
uintptr_t numExtraThreads; | ||
Thread *threads; | ||
}; | ||
|
||
/// Debugger hook. Calling this stops the debugger with a message and details | ||
/// about the issues. | ||
void reportToDebugger(bool isFatal, const char *message, | ||
RuntimeErrorDetails *details = nullptr); | ||
|
||
// namespace swift | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,23 +64,6 @@ static const char *getAccessName(ExclusivityFlags flags) { | |
} | ||
} | ||
|
||
LLVM_ATTRIBUTE_ALWAYS_INLINE | ||
static void printConflictDetails(const char *oldAccessName, void *oldPC, | ||
const char *newAccessName, void *newPC) { | ||
fprintf(stderr, "Previous access (a %s) started at ", oldAccessName); | ||
if (oldPC) { | ||
dumpStackTraceEntry(0, oldPC, /*shortOutput=*/true); | ||
fprintf(stderr, " (0x%lx).\n", (uintptr_t)oldPC); | ||
} else { | ||
fprintf(stderr, "<unknown>.\n"); | ||
} | ||
|
||
fprintf(stderr, "Current access (a %s) started at:\n", newAccessName); | ||
// The top frame is in swift_beginAccess, don't print it. | ||
constexpr unsigned framesToSkip = 2; | ||
printCurrentBacktrace(framesToSkip); | ||
} | ||
|
||
LLVM_ATTRIBUTE_ALWAYS_INLINE | ||
static void reportExclusivityConflict(ExclusivityFlags oldAction, void *oldPC, | ||
ExclusivityFlags newFlags, void *newPC, | ||
|
@@ -94,14 +77,53 @@ static void reportExclusivityConflict(ExclusivityFlags oldAction, void *oldPC, | |
return; | ||
} | ||
|
||
fprintf(stderr, | ||
"Simultaneous accesses to 0x%lx, but modification requires exclusive " | ||
"access.\n", | ||
(uintptr_t)pointer); | ||
printConflictDetails(getAccessName(oldAction), oldPC, | ||
getAccessName(getAccessAction(newFlags)), newPC); | ||
constexpr unsigned maxMessageLength = 100; | ||
constexpr unsigned maxAccessDescriptionLength = 50; | ||
char message[maxMessageLength]; | ||
snprintf(message, sizeof(message), | ||
"Simultaneous accesses to 0x%lx, but modification requires " | ||
"exclusive access", | ||
(uintptr_t)pointer); | ||
fprintf(stderr, "%s.\n", message); | ||
|
||
char oldAccess[maxAccessDescriptionLength]; | ||
snprintf(oldAccess, sizeof(oldAccess), | ||
"Previous access (a %s) started at", getAccessName(oldAction)); | ||
fprintf(stderr, "%s ", oldAccess); | ||
if (oldPC) { | ||
dumpStackTraceEntry(0, oldPC, /*shortOutput=*/true); | ||
fprintf(stderr, " (0x%lx).\n", (uintptr_t)oldPC); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this line-ending There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not trying to change the text output with this patch, so I think this is okay. Notice the
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, ok. |
||
} else { | ||
fprintf(stderr, "<unknown>.\n"); | ||
} | ||
|
||
char newAccess[maxAccessDescriptionLength]; | ||
snprintf(newAccess, sizeof(newAccess), "Current access (a %s) started at", | ||
getAccessName(getAccessAction(newFlags))); | ||
fprintf(stderr, "%s:\n", newAccess); | ||
// The top frame is in swift_beginAccess, don't print it. | ||
constexpr unsigned framesToSkip = 1; | ||
printCurrentBacktrace(framesToSkip); | ||
|
||
bool keepGoing = isWarningOnly(newFlags); | ||
|
||
RuntimeErrorDetails::Thread secondaryThread = { | ||
.description = oldAccess, | ||
.numFrames = 1, | ||
.frames = &oldPC | ||
}; | ||
RuntimeErrorDetails details = { | ||
.version = 1, | ||
.errorType = "exclusivity-violation", | ||
.currentStackDescription = newAccess, | ||
.framesToSkip = framesToSkip, | ||
.memoryAddress = pointer, | ||
.numExtraThreads = 1, | ||
.threads = &secondaryThread | ||
}; | ||
reportToDebugger(!keepGoing, message, &details); | ||
|
||
if (isWarningOnly(newFlags)) { | ||
if (keepGoing) { | ||
return; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#
of frames to skip?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good suggestion, I'll add it. It may get tricky to know the right value, though, because we need to know what is inlined and what is not. But it seems to be controllable enough in the two current use cases (implicit objc entrypoints and exclusivity violations).