Skip to content

Commit

Permalink
8343756: CAN_SHOW_REGISTERS_ON_ASSERT for Windows
Browse files Browse the repository at this point in the history
Reviewed-by: stuefe, jsjolen
  • Loading branch information
Markus Grönlund committed Nov 26, 2024
1 parent 3a625f3 commit 0054bbe
Show file tree
Hide file tree
Showing 11 changed files with 216 additions and 94 deletions.
42 changes: 41 additions & 1 deletion src/hotspot/os/posix/os_posix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2111,7 +2111,7 @@ void os::shutdown() {
// easily trigger secondary faults in those threads. To reduce the likelihood
// of that we use _exit rather than exit, so that no atexit hooks get run.
// But note that os::shutdown() could also trigger secondary faults.
void os::abort(bool dump_core, void* siginfo, const void* context) {
void os::abort(bool dump_core, const void* siginfo, const void* context) {
os::shutdown();
if (dump_core) {
LINUX_ONLY(if (DumpPrivateMappingsInCore) ClassLoader::close_jrt_image();)
Expand Down Expand Up @@ -2186,3 +2186,43 @@ char* os::pd_map_memory(int fd, const char* unused,
bool os::pd_unmap_memory(char* addr, size_t bytes) {
return munmap(addr, bytes) == 0;
}

#ifdef CAN_SHOW_REGISTERS_ON_ASSERT
static ucontext_t _saved_assert_context;
static bool _has_saved_context = false;
#endif // CAN_SHOW_REGISTERS_ON_ASSERT

void os::save_assert_context(const void* ucVoid) {
#ifdef CAN_SHOW_REGISTERS_ON_ASSERT
assert(ucVoid != nullptr, "invariant");
assert(!_has_saved_context, "invariant");
memcpy(&_saved_assert_context, ucVoid, sizeof(ucontext_t));
// on Linux ppc64, ucontext_t contains pointers into itself which have to be patched up
// after copying the context (see comment in sys/ucontext.h):
#if defined(PPC64)
*((void**)&_saved_assert_context.uc_mcontext.regs) = &(_saved_assert_context.uc_mcontext.gp_regs);
#elif defined(AMD64)
// In the copied version, fpregs should point to the copied contents.
// Sanity check: fpregs should point into the context.
if ((address)((const ucontext_t*)ucVoid)->uc_mcontext.fpregs > (address)ucVoid) {
size_t fpregs_offset = pointer_delta(((const ucontext_t*)ucVoid)->uc_mcontext.fpregs, ucVoid, 1);
if (fpregs_offset < sizeof(ucontext_t)) {
// Preserve the offset.
*((void**)&_saved_assert_context.uc_mcontext.fpregs) = (void*)((address)(void*)&_saved_assert_context + fpregs_offset);
}
}
#endif
_has_saved_context = true;
#endif // CAN_SHOW_REGISTERS_ON_ASSERT
}

const void* os::get_saved_assert_context(const void** sigInfo) {
#ifdef CAN_SHOW_REGISTERS_ON_ASSERT
assert(sigInfo != nullptr, "invariant");
*sigInfo = nullptr;
return _has_saved_context ? &_saved_assert_context : nullptr;
#endif
*sigInfo = nullptr;
return nullptr;
}

15 changes: 11 additions & 4 deletions src/hotspot/os/posix/signals_posix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -578,9 +578,8 @@ int JVM_HANDLE_XXX_SIGNAL(int sig, siginfo_t* info,

// Handle assertion poison page accesses.
#ifdef CAN_SHOW_REGISTERS_ON_ASSERT
if (!signal_was_handled &&
((sig == SIGSEGV || sig == SIGBUS) && info != nullptr && info->si_addr == g_assert_poison)) {
signal_was_handled = handle_assert_poison_fault(ucVoid, info->si_addr);
if (VMError::was_assert_poison_crash(info)) {
signal_was_handled = handle_assert_poison_fault(ucVoid);
}
#endif

Expand Down Expand Up @@ -1136,8 +1135,16 @@ static const char* get_signal_name(int sig, char* out, size_t outlen) {
}

void os::print_siginfo(outputStream* os, const void* si0) {
#ifdef CAN_SHOW_REGISTERS_ON_ASSERT
// If we are here because of an assert/guarantee, we suppress
// printing the siginfo, because it is only an implementation
// detail capturing the context for said assert/guarantee.
if (VMError::was_assert_poison_crash(si0)) {
return;
}
#endif

const siginfo_t* const si = (const siginfo_t*) si0;
const siginfo_t* const si = (const siginfo_t*)si0;

char buf[20];
os->print("siginfo:");
Expand Down
15 changes: 13 additions & 2 deletions src/hotspot/os/posix/vmError_posix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ static void crash_handler(int sig, siginfo_t* info, void* context) {

// Needed because asserts may happen in error handling too.
#ifdef CAN_SHOW_REGISTERS_ON_ASSERT
if ((sig == SIGSEGV || sig == SIGBUS) && info != nullptr && info->si_addr == g_assert_poison) {
if (handle_assert_poison_fault(context, info->si_addr)) {
if (VMError::was_assert_poison_crash(info)) {
if (handle_assert_poison_fault(context)) {
return;
}
}
Expand Down Expand Up @@ -127,3 +127,14 @@ void VMError::check_failing_cds_access(outputStream* st, const void* siginfo) {
}
#endif
}

bool VMError::was_assert_poison_crash(const void* siginfo) {
#ifdef CAN_SHOW_REGISTERS_ON_ASSERT
if (siginfo == nullptr) {
return false;
}
const siginfo_t* const si = (siginfo_t*)siginfo;
return (si->si_signo == SIGSEGV || si->si_signo == SIGBUS) && si->si_addr == g_assert_poison_read_only;
#endif
return false;
}
44 changes: 43 additions & 1 deletion src/hotspot/os/windows/os_windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
#include "services/runtimeService.hpp"
#include "symbolengine.hpp"
#include "utilities/align.hpp"
#include "utilities/debug.hpp"
#include "utilities/decoder.hpp"
#include "utilities/defaultStream.hpp"
#include "utilities/events.hpp"
Expand Down Expand Up @@ -1317,7 +1318,7 @@ void os::check_core_dump_prerequisites(char* buffer, size_t bufferSize, bool che
}
}

void os::abort(bool dump_core, void* siginfo, const void* context) {
void os::abort(bool dump_core, const void* siginfo, const void* context) {
EXCEPTION_POINTERS ep;
MINIDUMP_EXCEPTION_INFORMATION mei;
MINIDUMP_EXCEPTION_INFORMATION* pmei;
Expand Down Expand Up @@ -2112,7 +2113,17 @@ bool os::signal_sent_by_kill(const void* siginfo) {
}

void os::print_siginfo(outputStream *st, const void* siginfo) {
#ifdef CAN_SHOW_REGISTERS_ON_ASSERT
// If we are here because of an assert/guarantee, we suppress
// printing the siginfo, because it is only an implementation
// detail capturing the context for said assert/guarantee.
if (VMError::was_assert_poison_crash(siginfo)) {
return;
}
#endif

const EXCEPTION_RECORD* const er = (EXCEPTION_RECORD*)siginfo;

st->print("siginfo:");

char tmp[64];
Expand Down Expand Up @@ -2625,6 +2636,14 @@ LONG WINAPI topLevelExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) {
#endif
#endif

#ifdef CAN_SHOW_REGISTERS_ON_ASSERT
if (VMError::was_assert_poison_crash(exception_record)) {
if (handle_assert_poison_fault(exceptionInfo)) {
return EXCEPTION_CONTINUE_EXECUTION;
}
}
#endif

if (t != nullptr && t->is_Java_thread()) {
JavaThread* thread = JavaThread::cast(t);
bool in_java = thread->thread_state() == _thread_in_Java;
Expand Down Expand Up @@ -6165,3 +6184,26 @@ void os::print_user_info(outputStream* st) {
void os::print_active_locale(outputStream* st) {
// not implemented yet
}

static CONTEXT _saved_assert_context;
static EXCEPTION_RECORD _saved_exception_record;
static bool _has_saved_context = false;

void os::save_assert_context(const void* ucVoid) {
assert(ucVoid != nullptr, "invariant");
assert(!_has_saved_context, "invariant");
const EXCEPTION_POINTERS* ep = static_cast<const EXCEPTION_POINTERS*>(ucVoid);
memcpy(&_saved_assert_context, ep->ContextRecord, sizeof(CONTEXT));
memcpy(&_saved_exception_record, ep->ExceptionRecord, sizeof(EXCEPTION_RECORD));
_has_saved_context = true;
}

const void* os::get_saved_assert_context(const void** sigInfo) {
assert(sigInfo != nullptr, "invariant");
if (_has_saved_context) {
*sigInfo = &_saved_exception_record;
return &_saved_assert_context;
}
*sigInfo = nullptr;
return nullptr;
}
22 changes: 18 additions & 4 deletions src/hotspot/os/windows/vmError_windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "runtime/arguments.hpp"
#include "runtime/javaThread.hpp"
#include "runtime/os.hpp"
#include "utilities/debug.hpp"
#include "utilities/vmError.hpp"

LONG WINAPI crash_handler(struct _EXCEPTION_POINTERS* exceptionInfo) {
Expand Down Expand Up @@ -67,10 +68,23 @@ void VMError::check_failing_cds_access(outputStream* st, const void* siginfo) {
void VMError::reporting_started() {}
void VMError::interrupt_reporting_thread() {}

void VMError::raise_fail_fast(void* exrecord, void* context) {
void VMError::raise_fail_fast(const void* exrecord, const void* context) {
DWORD flags = (exrecord == nullptr) ? FAIL_FAST_GENERATE_EXCEPTION_ADDRESS : 0;
RaiseFailFastException(static_cast<PEXCEPTION_RECORD>(exrecord),
static_cast<PCONTEXT>(context),
flags);
PEXCEPTION_RECORD exception_record = static_cast<PEXCEPTION_RECORD>(const_cast<void*>(exrecord));
PCONTEXT ctx = static_cast<PCONTEXT>(const_cast<void*>(context));
RaiseFailFastException(exception_record, ctx, flags);
::abort();
}

bool VMError::was_assert_poison_crash(const void* siginfo) {
#ifdef CAN_SHOW_REGISTERS_ON_ASSERT
if (siginfo == nullptr) {
return false;
}
const EXCEPTION_RECORD* const er = (EXCEPTION_RECORD*)siginfo;
if (er->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && er->NumberParameters >= 2) {
return (void*)er->ExceptionInformation[1] == g_assert_poison_read_only;
}
#endif
return false;
}
6 changes: 5 additions & 1 deletion src/hotspot/share/runtime/os.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,10 @@ class os: AllStatic {
static frame fetch_frame_from_context(const void* ucVoid);
static frame fetch_compiled_frame_from_context(const void* ucVoid);

// For saving an os specific context generated by an assert or guarantee.
static void save_assert_context(const void* ucVoid);
static const void* get_saved_assert_context(const void** sigInfo);

static void breakpoint();
static bool start_debugging(char *buf, int buflen);

Expand All @@ -643,7 +647,7 @@ class os: AllStatic {

// Terminate with an error. Default is to generate a core file on platforms
// that support such things. This calls shutdown() and then aborts.
[[noreturn]] static void abort(bool dump_core, void *siginfo, const void *context);
[[noreturn]] static void abort(bool dump_core, const void *siginfo, const void *context);
[[noreturn]] static void abort(bool dump_core = true);

// Die immediately, no exit hook, no abort hook, no cleanup.
Expand Down
98 changes: 43 additions & 55 deletions src/hotspot/share/utilities/debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@
#ifdef CAN_SHOW_REGISTERS_ON_ASSERT
static char g_dummy;
char* g_assert_poison = &g_dummy;
const char* g_assert_poison_read_only = &g_dummy;
static intx g_asserting_thread = 0;
static void* g_assertion_context = nullptr;
#endif // CAN_SHOW_REGISTERS_ON_ASSERT

int DebuggingContext::_enabled = 0; // Initially disabled.
Expand Down Expand Up @@ -181,16 +181,21 @@ void report_vm_error(const char* file, int line, const char* error_msg, const ch
{
va_list detail_args;
va_start(detail_args, detail_fmt);
void* context = nullptr;

print_error_for_unit_test(error_msg, detail_fmt, detail_args);

const void* context = nullptr;
const void* siginfo = nullptr;

#ifdef CAN_SHOW_REGISTERS_ON_ASSERT
if (g_assertion_context != nullptr && os::current_thread_id() == g_asserting_thread) {
context = g_assertion_context;
if (os::current_thread_id() == g_asserting_thread) {
context = os::get_saved_assert_context(&siginfo);
}
#endif // CAN_SHOW_REGISTERS_ON_ASSERT

print_error_for_unit_test(error_msg, detail_fmt, detail_args);

VMError::report_and_die(Thread::current_or_null(), context, file, line, error_msg, detail_fmt, detail_args);
VMError::report_and_die(INTERNAL_ERROR, error_msg, detail_fmt, detail_args,
Thread::current_or_null(), nullptr, siginfo, context,
file, line, 0);
va_end(detail_args);
}

Expand All @@ -202,17 +207,21 @@ void report_vm_status_error(const char* file, int line, const char* error_msg,
void report_fatal(VMErrorType error_type, const char* file, int line, const char* detail_fmt, ...) {
va_list detail_args;
va_start(detail_args, detail_fmt);
void* context = nullptr;


print_error_for_unit_test("fatal error", detail_fmt, detail_args);

const void* context = nullptr;
const void* siginfo = nullptr;

#ifdef CAN_SHOW_REGISTERS_ON_ASSERT
if (g_assertion_context != nullptr && os::current_thread_id() == g_asserting_thread) {
context = g_assertion_context;
if (os::current_thread_id() == g_asserting_thread) {
context = os::get_saved_assert_context(&siginfo);
}
#endif // CAN_SHOW_REGISTERS_ON_ASSERT

print_error_for_unit_test("fatal error", detail_fmt, detail_args);

VMError::report_and_die(error_type, "fatal error", detail_fmt, detail_args,
Thread::current_or_null(), nullptr, nullptr, context,
Thread::current_or_null(), nullptr, siginfo, context,
file, line, 0);
va_end(detail_args);
}
Expand Down Expand Up @@ -705,16 +714,14 @@ struct TestMultipleStaticAssertFormsInClassScope {

// Support for showing register content on asserts/guarantees.
#ifdef CAN_SHOW_REGISTERS_ON_ASSERT

static ucontext_t g_stored_assertion_context;

void initialize_assert_poison() {
char* page = os::reserve_memory(os::vm_page_size());
if (page) {
MemTracker::record_virtual_memory_tag(page, mtInternal);
if (os::commit_memory(page, os::vm_page_size(), false) &&
os::protect_memory(page, os::vm_page_size(), os::MEM_PROT_NONE)) {
g_assert_poison = page;
g_assert_poison_read_only = page;
}
}
}
Expand All @@ -723,48 +730,29 @@ void disarm_assert_poison() {
g_assert_poison = &g_dummy;
}

static void store_context(const void* context) {
memcpy(&g_stored_assertion_context, context, sizeof(ucontext_t));
#if defined(LINUX)
// on Linux ppc64, ucontext_t contains pointers into itself which have to be patched up
// after copying the context (see comment in sys/ucontext.h):
#if defined(PPC64)
*((void**) &g_stored_assertion_context.uc_mcontext.regs) = &(g_stored_assertion_context.uc_mcontext.gp_regs);
#elif defined(AMD64)
// In the copied version, fpregs should point to the copied contents.
// Sanity check: fpregs should point into the context.
if ((address)((const ucontext_t*)context)->uc_mcontext.fpregs > (address)context) {
size_t fpregs_offset = pointer_delta(((const ucontext_t*)context)->uc_mcontext.fpregs, context, 1);
if (fpregs_offset < sizeof(ucontext_t)) {
// Preserve the offset.
*((void**) &g_stored_assertion_context.uc_mcontext.fpregs) = (void*)((address)(void*)&g_stored_assertion_context + fpregs_offset);
}
}
#endif
#endif
}

bool handle_assert_poison_fault(const void* ucVoid, const void* faulting_address) {
if (faulting_address == g_assert_poison) {
// Disarm poison page.
if (os::protect_memory((char*)g_assert_poison, os::vm_page_size(), os::MEM_PROT_RWX) == false) {
#ifdef ASSERT
fprintf(stderr, "Assertion poison page cannot be unprotected - mprotect failed with %d (%s)",
errno, os::strerror(errno));
fflush(stderr);
static void print_unprotect_error() {
fprintf(stderr, "Assertion poison page cannot be unprotected - mprotect failed with %d (%s)",
errno, os::strerror(errno));
fflush(stderr);
}
#endif
return false; // unprotecting memory may fail in OOM situations, as surprising as this sounds.
}
// Store Context away.
if (ucVoid) {
const intx my_tid = os::current_thread_id();
if (Atomic::cmpxchg(&g_asserting_thread, (intx)0, my_tid) == 0) {
store_context(ucVoid);
g_assertion_context = &g_stored_assertion_context;
}

// TOUCH_ASSERT_POISON writes to the protected g_assert_poison page, which faults
// and enters platform signal handlers which in turn invokes this routine.
bool handle_assert_poison_fault(const void* ucVoid) {
// Disarm poison page.
if (!os::protect_memory((char*)g_assert_poison, os::vm_page_size(), os::MEM_PROT_RWX)) {
DEBUG_ONLY(print_unprotect_error();)
return false; // unprotecting memory may fail in OOM situations, as surprising as this sounds.
}
if (ucVoid != nullptr) {
// Save context.
const intx my_tid = os::current_thread_id();
if (Atomic::cmpxchg(&g_asserting_thread, (intx)0, my_tid) == 0) {
os::save_assert_context(ucVoid);
}
return true;
}
return false;
return true;
}
#endif // CAN_SHOW_REGISTERS_ON_ASSERT
Loading

1 comment on commit 0054bbe

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

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

Please sign in to comment.