Skip to content

Add error dialog for critical errors on Windows #54836

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

Open
wants to merge 2 commits into
base: ct/more-fprint
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/src/devdocs/debuggingtips.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ The corresponding LLDB command is (after the process is started):
(lldb) pro hand -p true -s false -n false SIGSEGV
```

If you are debugging a segfault with threaded code, you can set a breakpoint on `jl_critical_error`
If you are debugging a segfault with threaded code, you can set a breakpoint on `jl_fprint_critical_error`
(`sigdie_handler` should also work on Linux and BSD) in order to only catch the actual segfault
rather than the GC synchronization points.

Expand Down
107 changes: 78 additions & 29 deletions src/signals-win.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,29 @@ void __cdecl crt_sig_handler(int sig, int num)
}
memset(&Context, 0, sizeof(Context));
RtlCaptureContext(&Context);

ios_t s;
ios_mem(&s, 1024);
if (sig == SIGILL)
jl_fprint_sigill(ios_stderr, &Context);
jl_fprint_critical_error(ios_stderr, sig, 0, &Context, jl_get_current_task());
jl_fprint_sigill(&s, &Context);
jl_fprint_critical_error(&s, sig, 0, &Context, jl_get_current_task());

// First write to stderr
ios_write_direct(ios_stderr, &s);

// Then write to Application log
HANDLE event_source = RegisterEventSourceW(NULL, L"julia");
if (event_source != INVALID_HANDLE_VALUE) {
ios_putc('\0', &s);
const wchar_t *strings[] = { ios_utf8_to_wchar(s.buf) };
ReportEventW(
event_source, EVENTLOG_ERROR_TYPE, /* category */ 0, /* event_id */ (DWORD)0xE0000000L,
/* user_sid */ NULL, /* n_strings */ 1, /* data_size */ 0, strings, /* data */ NULL
);
MessageBoxW(NULL, /* message */ L"error: libjulia received a fatal signal.\n\n"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know very little about Windows, so my apologies if these questions are dumb, but: 1. Is this call async, or does it block?
2. Is this really safe to call here? In particular,
3. I am slightly worried about situations I experienced in an entirely different context were broadly similar code could lead to hundreds of dialog boxes spamming a user (when code that launched a ton of subprocesses to process data had them all fail simultaneously -- e.g. because a network connection dropped).

Anyway, none of this is meant as argument against this PR (which to me, as a non-Windows user, seems quite useful). Feel free to ignore me :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On 1: it blocks the thread.

L"See Application log in Event Viewer for more information.",
/* title */ L"fatal error in libjulia", MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
}
raise(sig);
}
}
Expand Down Expand Up @@ -278,59 +298,88 @@ LONG WINAPI jl_exception_handler(struct _EXCEPTION_POINTERS *ExceptionInfo)
break;
}
}
ios_t full_error, summary;
ios_mem(&full_error, 1024);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calling malloc unconditionally here is a bit wasteful, since we will call it on demand when actually needed

Suggested change
ios_mem(&full_error, 1024);
ios_mem(&full_error, 0);

if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) {
jl_safe_printf("\n");
jl_fprint_sigill(ios_stderr, ExceptionInfo->ContextRecord);
jl_safe_fprintf(&full_error, "\n");
jl_fprint_sigill(&full_error, ExceptionInfo->ContextRecord);
}
jl_safe_printf("\nPlease submit a bug report with steps to reproduce this fault, and any error messages that follow (in their entirety). Thanks.\nException: ");
jl_safe_fprintf(&full_error, "\nPlease submit a bug report with steps to reproduce this fault, and any error messages that follow (in their entirety). Thanks.\n");
ios_mem(&summary, 128);
jl_safe_fprintf(&summary, "Exception: ");
switch (ExceptionInfo->ExceptionRecord->ExceptionCode) {
case EXCEPTION_ACCESS_VIOLATION:
jl_safe_printf("EXCEPTION_ACCESS_VIOLATION"); break;
jl_safe_fprintf(&summary, "EXCEPTION_ACCESS_VIOLATION"); break;
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
jl_safe_printf("EXCEPTION_ARRAY_BOUNDS_EXCEEDED"); break;
jl_safe_fprintf(&summary, "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"); break;
case EXCEPTION_BREAKPOINT:
jl_safe_printf("EXCEPTION_BREAKPOINT"); break;
jl_safe_fprintf(&summary, "EXCEPTION_BREAKPOINT"); break;
case EXCEPTION_DATATYPE_MISALIGNMENT:
jl_safe_printf("EXCEPTION_DATATYPE_MISALIGNMENT"); break;
jl_safe_fprintf(&summary, "EXCEPTION_DATATYPE_MISALIGNMENT"); break;
case EXCEPTION_FLT_DENORMAL_OPERAND:
jl_safe_printf("EXCEPTION_FLT_DENORMAL_OPERAND"); break;
jl_safe_fprintf(&summary, "EXCEPTION_FLT_DENORMAL_OPERAND"); break;
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
jl_safe_printf("EXCEPTION_FLT_DIVIDE_BY_ZERO"); break;
jl_safe_fprintf(&summary, "EXCEPTION_FLT_DIVIDE_BY_ZERO"); break;
case EXCEPTION_FLT_INEXACT_RESULT:
jl_safe_printf("EXCEPTION_FLT_INEXACT_RESULT"); break;
jl_safe_fprintf(&summary, "EXCEPTION_FLT_INEXACT_RESULT"); break;
case EXCEPTION_FLT_INVALID_OPERATION:
jl_safe_printf("EXCEPTION_FLT_INVALID_OPERATION"); break;
jl_safe_fprintf(&summary, "EXCEPTION_FLT_INVALID_OPERATION"); break;
case EXCEPTION_FLT_OVERFLOW:
jl_safe_printf("EXCEPTION_FLT_OVERFLOW"); break;
jl_safe_fprintf(&summary, "EXCEPTION_FLT_OVERFLOW"); break;
case EXCEPTION_FLT_STACK_CHECK:
jl_safe_printf("EXCEPTION_FLT_STACK_CHECK"); break;
jl_safe_fprintf(&summary, "EXCEPTION_FLT_STACK_CHECK"); break;
case EXCEPTION_FLT_UNDERFLOW:
jl_safe_printf("EXCEPTION_FLT_UNDERFLOW"); break;
jl_safe_fprintf(&summary, "EXCEPTION_FLT_UNDERFLOW"); break;
case EXCEPTION_ILLEGAL_INSTRUCTION:
jl_safe_printf("EXCEPTION_ILLEGAL_INSTRUCTION"); break;
jl_safe_fprintf(&summary, "EXCEPTION_ILLEGAL_INSTRUCTION"); break;
case EXCEPTION_IN_PAGE_ERROR:
jl_safe_printf("EXCEPTION_IN_PAGE_ERROR"); break;
jl_safe_fprintf(&summary, "EXCEPTION_IN_PAGE_ERROR"); break;
case EXCEPTION_INT_DIVIDE_BY_ZERO:
jl_safe_printf("EXCEPTION_INT_DIVIDE_BY_ZERO"); break;
jl_safe_fprintf(&summary, "EXCEPTION_INT_DIVIDE_BY_ZERO"); break;
case EXCEPTION_INT_OVERFLOW:
jl_safe_printf("EXCEPTION_INT_OVERFLOW"); break;
jl_safe_fprintf(&summary, "EXCEPTION_INT_OVERFLOW"); break;
case EXCEPTION_INVALID_DISPOSITION:
jl_safe_printf("EXCEPTION_INVALID_DISPOSITION"); break;
jl_safe_fprintf(&summary, "EXCEPTION_INVALID_DISPOSITION"); break;
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
jl_safe_printf("EXCEPTION_NONCONTINUABLE_EXCEPTION"); break;
jl_safe_fprintf(&summary, "EXCEPTION_NONCONTINUABLE_EXCEPTION"); break;
case EXCEPTION_PRIV_INSTRUCTION:
jl_safe_printf("EXCEPTION_PRIV_INSTRUCTION"); break;
jl_safe_fprintf(&summary, "EXCEPTION_PRIV_INSTRUCTION"); break;
case EXCEPTION_SINGLE_STEP:
jl_safe_printf("EXCEPTION_SINGLE_STEP"); break;
jl_safe_fprintf(&summary, "EXCEPTION_SINGLE_STEP"); break;
case EXCEPTION_STACK_OVERFLOW:
jl_safe_printf("EXCEPTION_STACK_OVERFLOW"); break;
jl_safe_fprintf(&summary, "EXCEPTION_STACK_OVERFLOW"); break;
default:
jl_safe_printf("UNKNOWN"); break;
jl_safe_fprintf(&summary, "UNKNOWN"); break;
}
jl_safe_fprintf(&summary, " at 0x%zx -- ", (size_t)ExceptionInfo->ExceptionRecord->ExceptionAddress);
jl_fprint_native_codeloc(&summary, (uintptr_t)ExceptionInfo->ExceptionRecord->ExceptionAddress);
ios_write(&full_error, summary.buf, ios_pos(&summary));

jl_fprint_critical_error(&full_error, 0, 0, ExceptionInfo->ContextRecord, ct);

// First print to STDERR
ios_write_direct(ios_stderr, &full_error);

// Secondly print to Application log
HANDLE event_source = RegisterEventSourceW(NULL, L"julia");
if (event_source != INVALID_HANDLE_VALUE) {
ios_putc('\0', &full_error);
const wchar_t *strings[] = {
ios_utf8_to_wchar(full_error.buf),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should free this

};
ReportEventW(
event_source, EVENTLOG_ERROR_TYPE, /* category */ 0, /* event_id */ (DWORD)0xE0000000L,
/* user_sid */ NULL, /* n_strings */ 1, /* data_size */ 0, strings, /* data */ NULL
);

ios_puts("\nSee Application log in Event Viewer for more information.\n", &summary);
ios_putc('\0', &summary);
MessageBoxW(NULL, /* message */ ios_utf8_to_wchar(summary.buf), /* title */ L"fatal error in libjulia",
MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
}
jl_safe_printf(" at 0x%zx -- ", (size_t)ExceptionInfo->ExceptionRecord->ExceptionAddress);
jl_fprint_native_codeloc(ios_stderr, (uintptr_t)ExceptionInfo->ExceptionRecord->ExceptionAddress);

jl_fprint_critical_error(ios_stderr, 0, 0, ExceptionInfo->ContextRecord, ct);
ios_close(&summary);
ios_close(&full_error);
static int recursion = 0;
if (recursion++)
exit(1);
Expand Down
2 changes: 0 additions & 2 deletions src/staticdata.c
Original file line number Diff line number Diff line change
Expand Up @@ -3053,8 +3053,6 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli
return;
}

JL_DLLEXPORT size_t ios_write_direct(ios_t *dest, ios_t *src);

// Takes in a path of the form "usr/lib/julia/sys.so" (jl_restore_system_image should be passed the same string)
JL_DLLEXPORT void jl_preload_sysimg_so(const char *fname)
{
Expand Down
18 changes: 18 additions & 0 deletions src/support/ios.c
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,24 @@ ios_t *ios_file(ios_t *s, const char *fname, int rd, int wr, int create, int tru
return NULL;
}

#ifdef _OS_WINDOWS_
const wchar_t *ios_utf8_to_wchar(const char *str) {
/* Fast-path empty strings, as MultiByteToWideChar() returns zero for them. */
if (str[0] == '\0') {
wchar_t *wstr = (wchar_t *)malloc(sizeof(wchar_t));
wstr[0] = L'\0';
return wstr;
}
size_t len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
if (!len)
return NULL;
wchar_t *wstr = (wchar_t *)malloc(len * sizeof(wchar_t));
if (!wstr || !MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len))
return NULL;
return wstr;
Comment on lines +993 to +1005
Copy link
Member

@vtjnash vtjnash Nov 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/* Fast-path empty strings, as MultiByteToWideChar() returns zero for them. */
if (str[0] == '\0') {
wchar_t *wstr = (wchar_t *)malloc(sizeof(wchar_t));
wstr[0] = L'\0';
return wstr;
}
size_t len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
if (!len)
return NULL;
wchar_t *wstr = (wchar_t *)malloc(len * sizeof(wchar_t));
if (!wstr || !MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len))
return NULL;
return wstr;
ssize_t wlen = uv_wtf8_length_as_utf16(str);
wchar_t *wstr = (wchar_t *)malloc_s(sizeof(wchar_t) * wlen);
uv_wtf8_to_utf16(str, wstr, wlen);
return wstr;

This is a more reliable (and concise) version of MultiByteToWideChar now provided by libuv

}
#endif // _OS_WINDOWS_

// Portable ios analogue of mkstemp: modifies fname to replace
// trailing XXXX's with unique ID and returns the file handle s
// for writing and reading.
Expand Down
5 changes: 5 additions & 0 deletions src/support/ios.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ extern void (*ios_set_io_wait_func)(int);
JL_DLLEXPORT size_t ios_read(ios_t *s, char *dest, size_t n) JL_NOTSAFEPOINT;
JL_DLLEXPORT size_t ios_readall(ios_t *s, char *dest, size_t n) JL_NOTSAFEPOINT;
JL_DLLEXPORT size_t ios_write(ios_t *s, const char *data, size_t n) JL_NOTSAFEPOINT;
JL_DLLEXPORT size_t ios_write_direct(ios_t *dest, ios_t *src) JL_NOTSAFEPOINT;
JL_DLLEXPORT int64_t ios_seek(ios_t *s, int64_t pos) JL_NOTSAFEPOINT; // absolute seek
JL_DLLEXPORT int64_t ios_seek_end(ios_t *s) JL_NOTSAFEPOINT;
JL_DLLEXPORT int64_t ios_skip(ios_t *s, int64_t offs); // relative seek
Expand Down Expand Up @@ -153,6 +154,10 @@ int ios_ungetc(int c, ios_t *s);
//wint_t ios_ungetwc(ios_t *s, wint_t wc);
#define ios_puts(str, s) ios_write(s, str, strlen(str))

#ifdef _OS_WINDOWS_
const wchar_t *ios_utf8_to_wchar(const char *str);
#endif

/*
With memory streams, mixed reads and writes are equivalent to performing
sequences of *p++, as either an lvalue or rvalue. File streams behave
Expand Down
31 changes: 27 additions & 4 deletions src/task.c
Original file line number Diff line number Diff line change
Expand Up @@ -689,11 +689,34 @@ JL_DLLEXPORT JL_NORETURN void jl_no_exc_handler(jl_value_t *e, jl_task_t *ct)
if (!e)
e = jl_current_exception(ct);

jl_printf((JL_STREAM*)STDERR_FILENO, "fatal: error thrown and no exception handler available.\n");
jl_static_show((JL_STREAM*)STDERR_FILENO, e);
jl_printf((JL_STREAM*)STDERR_FILENO, "\n");
jl_fprint_backtrace(ios_stderr);
// Write error to memory first
ios_t s;
ios_mem(&s, 1024);
jl_safe_fprintf(&s, "fatal: error thrown and no exception handler available.\n");
jl_static_show((JL_STREAM*)&s, e);
jl_safe_fprintf(&s, "\n");
jl_fprint_backtrace(&s);

// Then to STDERR
ios_write_direct(ios_stderr, &s);

// Finally write to system log (if supported)
#ifdef _OS_WINDOWS_
HANDLE event_source = RegisterEventSourceW(NULL, L"julia");
if (event_source != INVALID_HANDLE_VALUE) {
ios_putc('\0', &s);
const wchar_t *strings[] = { ios_utf8_to_wchar(s.buf) };
ReportEventW(
event_source, EVENTLOG_ERROR_TYPE, /* category */ 0, /* event_id */ (DWORD)0xE0000000L,
/* user_sid */ NULL, /* n_strings */ 1, /* data_size */ 0, strings, /* data */ NULL
);
MessageBoxW(NULL, /* message */ L"fatal: error thrown and no exception handler available\n\n"
L"See Application log in Event Viewer for more information.",
/* title */ L"fatal error in libjulia", MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
}
#endif

ios_close(&s);
if (ct == NULL)
jl_raise(6);
jl_exit(1);
Expand Down