8328306: AArch64: MacOS lazy JIT "write xor execute" switching

Co-authored-by: Dean Long <dlong@openjdk.org>
Reviewed-by: dlong, adinn
This commit is contained in:
Andrew Haley 2026-02-06 13:50:54 +00:00
parent d1b226dec2
commit 77e8469fb0
35 changed files with 436 additions and 74 deletions

View File

@ -4329,6 +4329,7 @@ public:
#undef INSN
Assembler(CodeBuffer* code) : AbstractAssembler(code) {
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
}
// Stack overflow checking

View File

@ -209,6 +209,10 @@ void BarrierSetNMethod::set_guard_value(nmethod* nm, int value, int bit_mask) {
bs_asm->increment_patching_epoch();
}
// Enable WXWrite: the function is called directly from nmethod_entry_barrier
// stub.
MACOS_AARCH64_ONLY(ThreadWXEnable wx(WXWrite, Thread::current()));
NativeNMethodBarrier barrier(nm);
barrier.set_value(value, bit_mask);
}

View File

@ -473,6 +473,7 @@ address MacroAssembler::target_addr_for_insn(address insn_addr) {
// Patch any kind of instruction; there may be several instructions.
// Return the total length (in bytes) of the instructions.
int MacroAssembler::pd_patch_instruction_size(address insn_addr, address target) {
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
return RelocActions<Patcher>::run(insn_addr, target);
}
@ -481,6 +482,8 @@ int MacroAssembler::patch_oop(address insn_addr, address o) {
unsigned insn = *(unsigned*)insn_addr;
assert(nativeInstruction_at(insn_addr+4)->is_movk(), "wrong insns in patch");
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
// OOPs are either narrow (32 bits) or wide (48 bits). We encode
// narrow OOPs by setting the upper 16 bits in the first
// instruction.
@ -510,6 +513,8 @@ int MacroAssembler::patch_narrow_klass(address insn_addr, narrowKlass n) {
assert(Instruction_aarch64::extract(insn->encoding(), 31, 21) == 0b11010010101 &&
nativeInstruction_at(insn_addr+4)->is_movk(), "wrong insns in patch");
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
Instruction_aarch64::patch(insn_addr, 20, 5, n >> 16);
Instruction_aarch64::patch(insn_addr+4, 20, 5, n & 0xffff);
return 2 * NativeInstruction::instruction_size;

View File

@ -133,7 +133,6 @@ void NativeMovConstReg::verify() {
intptr_t NativeMovConstReg::data() const {
// das(uint64_t(instruction_address()),2);
address addr = MacroAssembler::target_addr_for_insn(instruction_address());
if (maybe_cpool_ref(instruction_address())) {
return *(intptr_t*)addr;
@ -144,6 +143,7 @@ intptr_t NativeMovConstReg::data() const {
void NativeMovConstReg::set_data(intptr_t x) {
if (maybe_cpool_ref(instruction_address())) {
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
address addr = MacroAssembler::target_addr_for_insn(instruction_address());
*(intptr_t*)addr = x;
} else {
@ -350,8 +350,6 @@ bool NativeInstruction::is_stop() {
//-------------------------------------------------------------------
void NativeGeneralJump::verify() { }
// MT-safe patching of a long jump instruction.
void NativeGeneralJump::replace_mt_safe(address instr_addr, address code_buffer) {
ShouldNotCallThis();

View File

@ -90,16 +90,18 @@ protected:
s_char sbyte_at(int offset) const { return *(s_char*)addr_at(offset); }
u_char ubyte_at(int offset) const { return *(u_char*)addr_at(offset); }
jint int_at(int offset) const { return *(jint*)addr_at(offset); }
juint uint_at(int offset) const { return *(juint*)addr_at(offset); }
address ptr_at(int offset) const { return *(address*)addr_at(offset); }
oop oop_at(int offset) const { return *(oop*)addr_at(offset); }
jint int_at(int offset) const { return *(jint*)addr_at(offset); }
juint uint_at(int offset) const { return *(juint*)addr_at(offset); }
address ptr_at(int offset) const { return *(address*)addr_at(offset); }
oop oop_at(int offset) const { return *(oop*)addr_at(offset); }
void set_char_at(int offset, char c) { *addr_at(offset) = (u_char)c; }
void set_int_at(int offset, jint i) { *(jint*)addr_at(offset) = i; }
void set_uint_at(int offset, jint i) { *(juint*)addr_at(offset) = i; }
void set_ptr_at(int offset, address ptr) { *(address*)addr_at(offset) = ptr; }
void set_oop_at(int offset, oop o) { *(oop*)addr_at(offset) = o; }
#define MACOS_WX_WRITE MACOS_AARCH64_ONLY(os::thread_wx_enable_write())
void set_char_at(int offset, char c) { MACOS_WX_WRITE; *addr_at(offset) = (u_char)c; }
void set_int_at(int offset, jint i) { MACOS_WX_WRITE; *(jint*)addr_at(offset) = i; }
void set_uint_at(int offset, jint i) { MACOS_WX_WRITE; *(juint*)addr_at(offset) = i; }
void set_ptr_at(int offset, address ptr) { MACOS_WX_WRITE; *(address*)addr_at(offset) = ptr; }
void set_oop_at(int offset, oop o) { MACOS_WX_WRITE; *(oop*)addr_at(offset) = o; }
#undef MACOS_WX_WRITE
void wrote(int offset);
@ -380,7 +382,6 @@ public:
void set_jump_destination(address dest);
static void replace_mt_safe(address instr_addr, address code_buffer);
static void verify();
};
inline NativeGeneralJump* nativeGeneralJump_at(address address) {

View File

@ -11742,7 +11742,9 @@ class StubGenerator: public StubCodeGenerator {
}
#endif
StubRoutines::_unsafe_setmemory = generate_unsafe_setmemory();
if (vmIntrinsics::is_intrinsic_available(vmIntrinsics::_setMemory)) {
StubRoutines::_unsafe_setmemory = generate_unsafe_setmemory();
}
StubRoutines::aarch64::set_completed(); // Inidicate that arraycopy and zero_blocks stubs are generated
}

View File

@ -622,6 +622,22 @@ void VM_Version::initialize() {
check_virtualizations();
#ifdef __APPLE__
DefaultWXWriteMode = UseOldWX ? WXWrite : WXArmedForWrite;
if (TraceWXHealing) {
if (pthread_jit_write_protect_supported_np()) {
tty->print_cr("### TraceWXHealing is in use");
if (StressWXHealing) {
tty->print_cr("### StressWXHealing is in use");
}
} else {
tty->print_cr("WX Healing is not in use because MAP_JIT write protection "
"does not work on this system.");
}
}
#endif
// Sync SVE related CPU features with flags
if (UseSVE < 2) {
clear_feature(CPU_SVE2);

View File

@ -28,6 +28,7 @@
//
// Declare Bsd specific flags. They are not available on other platforms.
//
#ifdef AARCH64
#define RUNTIME_OS_FLAGS(develop, \
develop_pd, \
product, \
@ -35,9 +36,21 @@
range, \
constraint) \
\
AARCH64_ONLY(develop(bool, AssertWXAtThreadSync, true, \
"Conservatively check W^X thread state at possible safepoint" \
"or handshake"))
develop(bool, TraceWXHealing, false, \
"track occurrences of W^X mode healing") \
develop(bool, UseOldWX, false, \
"Choose old W^X implementation.") \
product(bool, StressWXHealing, false, DIAGNOSTIC, \
"Stress W xor X healing on MacOS")
#else
#define RUNTIME_OS_FLAGS(develop, \
develop_pd, \
product, \
product_pd, \
range, \
constraint)
#endif
// end of RUNTIME_OS_FLAGS

View File

@ -841,6 +841,7 @@ jlong os::javaTimeNanos() {
// We might also condition (c) on the magnitude of the delta between obsv and now.
// Avoiding excessive CAS operations to hot RW locations is critical.
// See https://blogs.oracle.com/dave/entry/cas_and_cache_trivia_invalidate
// https://web.archive.org/web/20131214182431/https://blogs.oracle.com/dave/entry/cas_and_cache_trivia_invalidate
return (prev == obsv) ? now : obsv;
}

View File

@ -54,8 +54,11 @@
#include "signals_posix.hpp"
#include "utilities/align.hpp"
#include "utilities/debug.hpp"
#include "utilities/decoder.hpp"
#include "utilities/events.hpp"
#include "utilities/nativeStackPrinter.hpp"
#include "utilities/vmError.hpp"
#include "compiler/disassembler.hpp"
// put OS-includes here
# include <sys/types.h>
@ -85,6 +88,8 @@
#define SPELL_REG_SP "sp"
#ifdef __APPLE__
WXMode DefaultWXWriteMode;
// see darwin-xnu/osfmk/mach/arm/_structs.h
// 10.5 UNIX03 member name prefixes
@ -233,19 +238,56 @@ NOINLINE frame os::current_frame() {
bool PosixSignals::pd_hotspot_signal_handler(int sig, siginfo_t* info,
ucontext_t* uc, JavaThread* thread) {
// Enable WXWrite: this function is called by the signal handler at arbitrary
// point of execution.
ThreadWXEnable wx(WXWrite, thread);
// decide if this trap can be handled by a stub
address stub = nullptr;
address pc = nullptr;
address pc = nullptr;
//%note os_trap_1
if (info != nullptr && uc != nullptr && thread != nullptr) {
pc = (address) os::Posix::ucontext_get_pc(uc);
#ifdef MACOS_AARCH64
// If we got a SIGBUS because we tried to write into the code
// cache, try enabling WXWrite mode.
if (sig == SIGBUS
&& pc != info->si_addr
&& CodeCache::contains(info->si_addr)
&& os::address_is_in_vm(pc)) {
WXMode *entry_mode = thread->_cur_wx_mode;
if (entry_mode != nullptr && *entry_mode == WXArmedForWrite) {
if (TraceWXHealing) {
static const char *mode_names[3] = {"WXWrite", "WXExec", "WXArmedForWrite"};
tty->print("Healing WXMode %s at %p to WXWrite",
mode_names[*entry_mode], entry_mode);
char name[128];
int offset = 0;
if (os::dll_address_to_function_name(pc, name, sizeof name, &offset)) {
tty->print_cr(" (%s+0x%x)", name, offset);
} else {
tty->cr();
}
if (Verbose) {
char buf[O_BUFLEN];
NativeStackPrinter nsp(thread);
nsp.print_stack(tty, buf, sizeof(buf), pc,
true /* print_source_info */, -1 /* max stack */);
}
}
#ifndef PRODUCT
guarantee(StressWXHealing,
"We should not reach here unless StressWXHealing");
#endif
*(thread->_cur_wx_mode) = WXWrite;
return thread->wx_enable_write();
}
}
// There may be cases where code after this point that we call
// from the signal handler changes WX state, so we protect against
// that by saving and restoring the state.
ThreadWXEnable wx(thread->get_wx_state(), thread);
#endif
// Handle ALL stack overflow variations here
if (sig == SIGSEGV || sig == SIGBUS) {
address addr = (address) info->si_addr;
@ -515,11 +557,42 @@ int os::extra_bang_size_in_bytes() {
return 0;
}
#ifdef __APPLE__
#ifdef MACOS_AARCH64
THREAD_LOCAL bool os::_jit_exec_enabled;
// This is a wrapper around the standard library function
// pthread_jit_write_protect_np(3). We keep track of the state of
// per-thread write protection on the MAP_JIT region in the
// thread-local variable os::_jit_exec_enabled
void os::current_thread_enable_wx(WXMode mode) {
pthread_jit_write_protect_np(mode == WXExec);
bool exec_enabled = mode != WXWrite;
if (exec_enabled != _jit_exec_enabled NOT_PRODUCT( || DefaultWXWriteMode == WXWrite)) {
permit_forbidden_function::pthread_jit_write_protect_np(exec_enabled);
_jit_exec_enabled = exec_enabled;
}
}
#endif
// If the current thread is in the WX state WXArmedForWrite, change
// the state to WXWrite.
bool Thread::wx_enable_write() {
if (_wx_state == WXArmedForWrite) {
_wx_state = WXWrite;
os::current_thread_enable_wx(WXWrite);
return true;
} else {
return false;
}
}
// A wrapper around wx_enable_write() for when the current thread is
// not known.
void os::thread_wx_enable_write_impl() {
if (!StressWXHealing) {
Thread::current()->wx_enable_write();
}
}
#endif // MACOS_AARCH64
static inline void atomic_copy64(const volatile void *src, volatile void *dst) {
*(jlong *) dst = *(const jlong *) src;

View File

@ -98,6 +98,8 @@ CodeBuffer::CodeBuffer(const CodeBlob* blob) DEBUG_ONLY(: Scrubber(this, sizeof(
}
void CodeBuffer::initialize(csize_t code_size, csize_t locs_size) {
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
// Always allow for empty slop around each section.
int slop = (int) CodeSection::end_slop();

View File

@ -541,6 +541,7 @@ extern void vm_exit(int code);
// unpack_with_exception entry instead. This makes life for the exception blob easier
// because making that same check and diverting is painful from assembly language.
JRT_ENTRY_NO_ASYNC(static address, exception_handler_for_pc_helper(JavaThread* current, oopDesc* ex, address pc, nmethod*& nm))
MACOS_AARCH64_ONLY(current->wx_enable_write());
Handle exception(current, ex);
// This function is called when we are about to throw an exception. Therefore,

View File

@ -127,6 +127,7 @@ PerfCounter* ClassLoader::_perf_ik_link_methods_count = nullptr;
PerfCounter* ClassLoader::_perf_method_adapters_count = nullptr;
PerfCounter* ClassLoader::_unsafe_defineClassCallCounter = nullptr;
PerfCounter* ClassLoader::_perf_secondary_hash_time = nullptr;
PerfCounter* ClassLoader::_perf_change_wx_time = nullptr;
PerfCounter* ClassLoader::_perf_resolve_indy_time = nullptr;
PerfCounter* ClassLoader::_perf_resolve_invokehandle_time = nullptr;
@ -1370,6 +1371,7 @@ void ClassLoader::initialize(TRAPS) {
NEWPERFBYTECOUNTER(_perf_sys_classfile_bytes_read, SUN_CLS, "sysClassBytes");
NEWPERFEVENTCOUNTER(_unsafe_defineClassCallCounter, SUN_CLS, "unsafeDefineClassCalls");
NEWPERFTICKCOUNTER(_perf_secondary_hash_time, SUN_CLS, "secondarySuperHashTime");
NEWPERFTICKCOUNTER(_perf_change_wx_time, SUN_CLS, "changeWXTime");
if (log_is_enabled(Info, perf, class, link)) {
NEWPERFTICKCOUNTER(_perf_ik_link_methods_time, SUN_CLS, "linkMethodsTime");

View File

@ -184,6 +184,7 @@ class ClassLoader: AllStatic {
// Count the time taken to hash the scondary superclass arrays.
static PerfCounter* _perf_secondary_hash_time;
static PerfCounter* _perf_change_wx_time;
// The boot class path consists of 3 ordered pieces:
// 1. the module/path pairs specified to --patch-module
@ -268,6 +269,9 @@ class ClassLoader: AllStatic {
static PerfCounter* perf_secondary_hash_time() {
return _perf_secondary_hash_time;
}
static PerfCounter* perf_change_wx_time() {
return _perf_change_wx_time;
}
static PerfCounter* perf_sys_classload_time() { return _perf_sys_classload_time; }
static PerfCounter* perf_app_classload_time() { return _perf_app_classload_time; }
static PerfCounter* perf_app_classload_selftime() { return _perf_app_classload_selftime; }

View File

@ -520,6 +520,8 @@ VtableBlob* VtableBlob::create(const char* name, int buffer_size) {
// eventually.
return nullptr;
}
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
blob = new (size) VtableBlob(name, size);
CodeCache_lock->unlock();
}

View File

@ -2137,6 +2137,9 @@ void nmethod::make_deoptimized() {
ResourceMark rm;
RelocIterator iter(this, oops_reloc_begin());
// Assume there will be some calls to make deoptimized.
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
while (iter.next()) {
switch (iter.type()) {
@ -2213,6 +2216,7 @@ void nmethod::verify_clean_inline_caches() {
}
void nmethod::mark_as_maybe_on_stack() {
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
AtomicAccess::store(&_gc_epoch, CodeCache::gc_epoch());
}
@ -2305,6 +2309,8 @@ bool nmethod::make_not_entrant(InvalidationReason invalidation_reason) {
return false;
}
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
{
// Enter critical section. Does not block for safepoint.
ConditionalMutexLocker ml(NMethodState_lock, !NMethodState_lock->owned_by_self(), Mutex::_no_safepoint_check_flag);
@ -2740,6 +2746,8 @@ bool nmethod::is_unloading() {
state_is_unloading = IsUnloadingBehaviour::is_unloading(this);
uint8_t new_state = IsUnloadingState::create(state_is_unloading, state_unloading_cycle);
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
// Note that if an nmethod has dead oops, everyone will agree that the
// nmethod is_unloading. However, the is_cold heuristics can yield
// different outcomes, so we guard the computed result with a CAS

View File

@ -51,6 +51,9 @@ VMReg VtableStub::_receiver_location = VMRegImpl::Bad();
void* VtableStub::operator new(size_t size, int code_size) throw() {
assert_lock_strong(VtableStubs_lock);
assert(size == sizeof(VtableStub), "mismatched size");
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
// compute real VtableStub size (rounded to nearest word)
const int real_size = align_up(code_size + (int)sizeof(VtableStub), wordSize);
// malloc them in chunks to minimize header overhead

View File

@ -111,6 +111,8 @@ bool BarrierSetNMethod::nmethod_entry_barrier(nmethod* nm) {
return true;
}
// Enable WXWrite: the function is called directly from nmethod_entry_barrier
// stub.
MACOS_AARCH64_ONLY(ThreadWXEnable wx(WXWrite, Thread::current()));
// If the nmethod is the only thing pointing to the oops, and we are using a

View File

@ -163,6 +163,7 @@ void CodeHeap::mark_segmap_as_used(size_t beg, size_t end, bool is_FreeBlock_joi
void CodeHeap::invalidate(size_t beg, size_t end, size_t hdr_size) {
#ifndef PRODUCT
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
// Fill the given range with some bad value.
// length is expected to be in segment_size units.
// This prevents inadvertent execution of code leftover from previous use.
@ -172,11 +173,13 @@ void CodeHeap::invalidate(size_t beg, size_t end, size_t hdr_size) {
}
void CodeHeap::clear(size_t beg, size_t end) {
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
mark_segmap_as_free(beg, end);
invalidate(beg, end, 0);
}
void CodeHeap::clear() {
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
_next_segment = 0;
clear(_next_segment, _number_of_committed_segments);
}
@ -190,6 +193,7 @@ static size_t align_to_page_size(size_t size) {
bool CodeHeap::reserve(ReservedSpace rs, size_t committed_size, size_t segment_size) {
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
assert(rs.size() >= committed_size, "reserved < committed");
assert(is_aligned(committed_size, rs.page_size()), "must be page aligned");
assert(segment_size >= sizeof(FreeBlock), "segment size is too small");
@ -230,6 +234,7 @@ bool CodeHeap::reserve(ReservedSpace rs, size_t committed_size, size_t segment_s
bool CodeHeap::expand_by(size_t size) {
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
assert_locked_or_safepoint(CodeCache_lock);
// expand _memory space
@ -259,6 +264,7 @@ bool CodeHeap::expand_by(size_t size) {
void* CodeHeap::allocate(size_t instance_size) {
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
size_t number_of_segments = size_to_segments(instance_size + header_size());
assert(segments_to_size(number_of_segments) >= sizeof(FreeBlock), "not enough room for FreeList");
assert_locked_or_safepoint(CodeCache_lock);

View File

@ -1865,6 +1865,8 @@ JRT_ENTRY_NO_ASYNC(address, OptoRuntime::handle_exception_C_helper(JavaThread* c
// has updated oops.
StackWatermarkSet::after_unwind(current);
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
// Do not confuse exception_oop with pending_exception. The exception_oop
// is only used to pass arguments into the method. Not for general
// exception handling. DO NOT CHANGE IT to use pending_exception, since

View File

@ -26,6 +26,7 @@
#include "runtime/interfaceSupport.inline.hpp"
JVM_ENTRY(static jboolean, UH_FreeUpcallStub0(JNIEnv *env, jobject _unused, jlong addr))
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
// safe to call 'find_blob' without code cache lock, because stub is always alive
CodeBlob* cb = CodeCache::find_blob((char*)addr);
if (cb == nullptr) {

View File

@ -132,6 +132,7 @@ void DeoptimizationScope::mark(nmethod* nm, bool inc_recompile_counts) {
return;
}
MACOS_AARCH64_ONLY(os::thread_wx_enable_write());
nmethod::DeoptimizationStatus status =
inc_recompile_counts ? nmethod::deoptimize : nmethod::deoptimize_noupdate;
AtomicAccess::store(&nm->_deoptimization_status, status);

View File

@ -279,12 +279,14 @@ class VMNativeEntryWrapper {
os::verify_stack_alignment(); \
/* begin of body */
#define JRT_ENTRY(result_type, header) \
result_type header { \
assert(current == JavaThread::current(), "Must be"); \
MACOS_AARCH64_ONLY(ThreadWXEnable __wx(WXWrite, current)); \
ThreadInVMfromJava __tiv(current); \
MACOS_AARCH64_ONLY( \
static WXMode wx_mode = DefaultWXWriteMode; \
ThreadWXEnable __wx(&wx_mode, current); \
) \
VM_ENTRY_BASE(result_type, header, current) \
DEBUG_ONLY(VMEntryWrapper __vew;)
@ -311,8 +313,11 @@ class VMNativeEntryWrapper {
#define JRT_ENTRY_NO_ASYNC(result_type, header) \
result_type header { \
assert(current == JavaThread::current(), "Must be"); \
MACOS_AARCH64_ONLY(ThreadWXEnable __wx(WXWrite, current)); \
ThreadInVMfromJava __tiv(current, false /* check asyncs */); \
MACOS_AARCH64_ONLY( \
static WXMode wx_mode = DefaultWXWriteMode; \
ThreadWXEnable __wx(&wx_mode, current); \
) \
VM_ENTRY_BASE(result_type, header, current) \
DEBUG_ONLY(VMEntryWrapper __vew;)
@ -321,7 +326,10 @@ class VMNativeEntryWrapper {
#define JRT_BLOCK_ENTRY(result_type, header) \
result_type header { \
assert(current == JavaThread::current(), "Must be"); \
MACOS_AARCH64_ONLY(ThreadWXEnable __wx(WXWrite, current)); \
MACOS_AARCH64_ONLY( \
static WXMode wx_mode = DefaultWXWriteMode; \
ThreadWXEnable __wx(&wx_mode, current); \
) \
HandleMarkCleaner __hm(current);
#define JRT_BLOCK \
@ -358,8 +366,11 @@ extern "C" { \
result_type JNICALL header { \
JavaThread* thread=JavaThread::thread_from_jni_environment(env); \
assert(thread == Thread::current(), "JNIEnv is only valid in same thread"); \
MACOS_AARCH64_ONLY(ThreadWXEnable __wx(WXWrite, thread)); \
ThreadInVMfromNative __tiv(thread); \
MACOS_AARCH64_ONLY( \
static WXMode wx_mode = DefaultWXWriteMode; \
ThreadWXEnable __wx(&wx_mode, thread); \
) \
DEBUG_ONLY(VMNativeEntryWrapper __vew;) \
VM_ENTRY_BASE(result_type, header, thread)
@ -383,8 +394,11 @@ extern "C" { \
extern "C" { \
result_type JNICALL header { \
JavaThread* thread=JavaThread::thread_from_jni_environment(env); \
MACOS_AARCH64_ONLY(ThreadWXEnable __wx(WXWrite, thread)); \
ThreadInVMfromNative __tiv(thread); \
MACOS_AARCH64_ONLY( \
static WXMode wx_mode = DefaultWXWriteMode; \
ThreadWXEnable __wx(&wx_mode, thread); \
) \
DEBUG_ONLY(VMNativeEntryWrapper __vew;) \
VM_ENTRY_BASE(result_type, header, thread)
@ -393,8 +407,11 @@ extern "C" { \
extern "C" { \
result_type JNICALL header { \
JavaThread* thread = JavaThread::current(); \
MACOS_AARCH64_ONLY(ThreadWXEnable __wx(WXWrite, thread)); \
ThreadInVMfromNative __tiv(thread); \
MACOS_AARCH64_ONLY( \
static WXMode wx_mode = DefaultWXWriteMode; \
ThreadWXEnable __wx(&wx_mode, thread); \
) \
DEBUG_ONLY(VMNativeEntryWrapper __vew;) \
VM_ENTRY_BASE(result_type, header, thread)

View File

@ -94,16 +94,12 @@ JavaCallWrapper::JavaCallWrapper(const methodHandle& callee_method, Handle recei
DEBUG_ONLY(_thread->inc_java_call_counter());
_thread->set_active_handles(new_handles); // install new handle block and reset Java frame linkage
MACOS_AARCH64_ONLY(_thread->enable_wx(WXExec));
}
JavaCallWrapper::~JavaCallWrapper() {
assert(_thread == JavaThread::current(), "must still be the same thread");
MACOS_AARCH64_ONLY(_thread->enable_wx(WXWrite));
// restore previous handle block & Java frame linkage
JNIHandleBlock *_old_handles = _thread->active_handles();
_thread->set_active_handles(_handles);
@ -413,17 +409,20 @@ void JavaCalls::call_helper(JavaValue* result, const methodHandle& method, JavaC
#endif
}
}
StubRoutines::call_stub()(
(address)&link,
// (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
result_val_address, // see NOTE above (compiler problem)
result_type,
method(),
entry_point,
parameter_address,
args->size_of_parameters(),
CHECK
);
{
MACOS_AARCH64_ONLY(ThreadWXEnable wx(WXExec, thread));
StubRoutines::call_stub()(
(address)&link,
// (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
result_val_address, // see NOTE above (compiler problem)
result_type,
method(),
entry_point,
parameter_address,
args->size_of_parameters(),
CHECK
);
}
result = link.result(); // circumvent MS C++ 5.0 compiler bug (result is clobbered across call)
// Preserve oop return value across possible gc points

View File

@ -377,15 +377,6 @@ void JavaThread::check_possible_safepoint() {
// Clear unhandled oops in JavaThreads so we get a crash right away.
clear_unhandled_oops();
#endif // CHECK_UNHANDLED_OOPS
// Macos/aarch64 should be in the right state for safepoint (e.g.
// deoptimization needs WXWrite). Crashes caused by the wrong state rarely
// happens in practice, making such issues hard to find and reproduce.
#if defined(__APPLE__) && defined(AARCH64)
if (AssertWXAtThreadSync) {
assert_wx_state(WXWrite);
}
#endif
}
void JavaThread::check_for_valid_safepoint_state() {
@ -521,6 +512,11 @@ JavaThread::JavaThread(MemTag mem_tag) :
_last_freeze_fail_result(freeze_ok),
#endif
#ifdef MACOS_AARCH64
_cur_wx_enable(nullptr),
_cur_wx_mode(0),
#endif
_lock_stack(this),
_om_cache(this) {
set_jni_functions(jni_functions());

View File

@ -81,6 +81,7 @@ class JavaThread;
typedef void (*ThreadFunction)(JavaThread*, TRAPS);
class EventVirtualThreadPinned;
class ThreadWXEnable;
class JavaThread: public Thread {
friend class VMStructs;
@ -1288,6 +1289,15 @@ public:
bool get_and_clear_interrupted();
private:
#ifdef MACOS_AARCH64
friend class ThreadWXEnable;
friend class PosixSignals;
ThreadWXEnable* _cur_wx_enable;
WXMode* _cur_wx_mode;
#endif
LockStack _lock_stack;
OMCache _om_cache;

View File

@ -140,11 +140,16 @@ enum ThreadPriority { // JLS 20.20.1-3
CriticalPriority = 11 // Critical thread priority
};
#ifdef MACOS_AARCH64
enum WXMode {
WXWrite,
WXExec
WXWrite = 0,
WXExec = 1,
WXArmedForWrite = 2,
};
extern WXMode DefaultWXWriteMode;
#endif // MACOS_AARCH64
// Executable parameter flag for os::commit_memory() and
// os::commit_memory_or_exit().
const bool ExecMem = true;
@ -1128,9 +1133,23 @@ class os: AllStatic {
static char* build_agent_function_name(const char *sym, const char *cname,
bool is_absolute_path);
#if defined(__APPLE__) && defined(AARCH64)
#ifdef MACOS_AARCH64
// Enables write or execute access to writeable and executable pages.
static void current_thread_enable_wx(WXMode mode);
// Macos-AArch64 only.
static void thread_wx_enable_write_impl();
// Short circuit write enabling if it's already enabled. This
// function is executed many times, so it makes sense to inline a
// small part of it.
private:
static THREAD_LOCAL bool _jit_exec_enabled;
public:
static void thread_wx_enable_write() {
if (__builtin_expect(_jit_exec_enabled, false)) {
thread_wx_enable_write_impl();
}
}
#endif // __APPLE__ && AARCH64
protected:

View File

@ -602,18 +602,21 @@ protected:
jint _hashStateY;
jint _hashStateZ;
#if defined(__APPLE__) && defined(AARCH64)
#ifdef MACOS_AARCH64
private:
DEBUG_ONLY(bool _wx_init);
WXMode _wx_state;
public:
void init_wx();
WXMode enable_wx(WXMode new_state);
bool wx_enable_write();
void assert_wx_state(WXMode expected) {
assert(_wx_state == expected, "wrong state");
}
#endif // __APPLE__ && AARCH64
WXMode get_wx_state() {
return _wx_state;
}
#endif // MACOS_AARCH64
private:
bool _in_asgct = false;

View File

@ -30,8 +30,9 @@
#include "gc/shared/tlab_globals.hpp"
#include "runtime/atomicAccess.hpp"
#include "utilities/permitForbiddenFunctions.hpp"
#if defined(__APPLE__) && defined(AARCH64)
#ifdef MACOS_AARCH64
#include "runtime/os.hpp"
#endif
@ -60,11 +61,17 @@ inline void Thread::set_threads_hazard_ptr(ThreadsList* new_list) {
}
#if defined(__APPLE__) && defined(AARCH64)
static void dummy() { }
inline void Thread::init_wx() {
assert(this == Thread::current(), "should only be called for current thread");
assert(!_wx_init, "second init");
_wx_state = WXWrite;
permit_forbidden_function::pthread_jit_write_protect_np(false);
os::current_thread_enable_wx(_wx_state);
// Side effect: preload base address of libjvm
guarantee(os::address_is_in_vm(CAST_FROM_FN_PTR(address, &dummy)), "must be");
DEBUG_ONLY(_wx_init = true);
}
@ -74,10 +81,19 @@ inline WXMode Thread::enable_wx(WXMode new_state) {
WXMode old = _wx_state;
if (_wx_state != new_state) {
_wx_state = new_state;
os::current_thread_enable_wx(new_state);
switch (new_state) {
case WXWrite:
case WXExec:
os::current_thread_enable_wx(new_state);
break;
case WXArmedForWrite:
break;
default: ShouldNotReachHere(); break;
}
}
return old;
}
#endif // __APPLE__ && AARCH64
#endif // SHARE_RUNTIME_THREAD_INLINE_HPP

View File

@ -28,25 +28,62 @@
// No threadWXSetters.hpp
#if defined(__APPLE__) && defined(AARCH64)
#ifdef MACOS_AARCH64
#include "classfile/classLoader.hpp"
#include "runtime/perfData.inline.hpp"
#include "runtime/thread.inline.hpp"
class ThreadWXEnable {
Thread* _thread;
WXMode _old_mode;
WXMode *_this_wx_mode;
ThreadWXEnable *_prev;
public:
ThreadWXEnable(WXMode new_mode, Thread* thread) :
_thread(thread),
_old_mode(_thread ? _thread->enable_wx(new_mode) : WXWrite)
{ }
~ThreadWXEnable() {
if (_thread) {
_thread->enable_wx(_old_mode);
ThreadWXEnable(WXMode* new_mode, Thread* thread) :
_thread(thread), _this_wx_mode(new_mode) {
NOT_PRODUCT(PerfTraceTime ptt(ClassLoader::perf_change_wx_time());)
JavaThread* javaThread
= _thread && _thread->is_Java_thread()
? JavaThread::cast(_thread) : nullptr;
_prev = javaThread != nullptr ? javaThread->_cur_wx_enable: nullptr;
_old_mode = _thread != nullptr ? _thread->enable_wx(*new_mode) : WXWrite;
if (javaThread != nullptr) {
javaThread->_cur_wx_enable = this;
javaThread->_cur_wx_mode = new_mode;
}
}
ThreadWXEnable(WXMode new_mode, Thread* thread) :
_thread(thread), _this_wx_mode(nullptr) {
NOT_PRODUCT(PerfTraceTime ptt(ClassLoader::perf_change_wx_time());)
JavaThread* javaThread
= _thread && _thread->is_Java_thread()
? JavaThread::cast(_thread) : nullptr;
_prev = javaThread != nullptr ? javaThread->_cur_wx_enable: nullptr;
_old_mode = _thread != nullptr ? _thread->enable_wx(new_mode) : WXWrite;
if (javaThread) {
javaThread->_cur_wx_enable = this;
javaThread->_cur_wx_mode = nullptr;
}
}
~ThreadWXEnable() {
NOT_PRODUCT(PerfTraceTime ptt(ClassLoader::perf_change_wx_time());)
if (_thread) {
_thread->enable_wx(_old_mode);
JavaThread* javaThread
= _thread && _thread->is_Java_thread()
? JavaThread::cast(_thread) : nullptr;
if (javaThread != nullptr) {
javaThread->_cur_wx_enable = _prev;
javaThread->_cur_wx_mode = _prev != nullptr ? _prev->_this_wx_mode : nullptr;
}
}
}
static bool test(address p);
};
#endif // __APPLE__ && AARCH64
#endif // MACOS_AARCH64
#endif // SHARE_RUNTIME_THREADWXSETTERS_INLINE_HPP

View File

@ -63,4 +63,8 @@ PRAGMA_DIAG_POP
FORBID_IMPORTED_C_FUNCTION(char* strdup(const char *s), noexcept, "use os::strdup");
FORBID_IMPORTED_C_FUNCTION(wchar_t* wcsdup(const wchar_t *s), noexcept, "don't use");
// Disallow non-wrapped raw library function.
MACOS_AARCH64_ONLY(FORBID_C_FUNCTION(void pthread_jit_write_protect_np(int enabled), noexcept, \
"use os::current_thread_enable_wx");)
#endif // SHARE_UTILITIES_FORBIDDENFUNCTIONS_HPP

View File

@ -548,6 +548,9 @@
#endif
#define MACOS_AARCH64_ONLY(x) MACOS_ONLY(AARCH64_ONLY(x))
#if defined(__APPLE__) && defined(AARCH64)
#define MACOS_AARCH64 1
#endif
#if defined(RISCV32) || defined(RISCV64)
#define RISCV

View File

@ -70,6 +70,10 @@ inline void* realloc(void* ptr, size_t size) { return ::realloc(ptr, size); }
inline char* strdup(const char* s) { return ::strdup(s); }
MACOS_AARCH64_ONLY( \
inline void pthread_jit_write_protect_np(int enabled) { return ::pthread_jit_write_protect_np(enabled); } \
)
END_ALLOW_FORBIDDEN_FUNCTIONS
} // namespace permit_forbidden_function

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2025 IBM Corporation. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @requires os.family == "mac"
* @requires os.arch == "aarch64"
* @summary Run shell with -XX:+StressWXHealing. This tests most of
* the triggers for WX mode.
* @library /test/lib
* @compile WXHealing.java
* @run main TestWXHealing
*/
import java.util.regex.*;
import jdk.test.lib.process.*;
import static java.nio.charset.StandardCharsets.*;
public class TestWXHealing {
public static void main(String[] args) throws Throwable {
String[] opts = {"-XX:+UnlockDiagnosticVMOptions",
"-XX:+TraceWXHealing", "-XX:+StressWXHealing", "WXHealing"};
var process = ProcessTools.createTestJavaProcessBuilder(opts).start();
String output = new String(process.getInputStream().readAllBytes(), UTF_8);
System.out.println(output);
if (output.contains("MAP_JIT write protection does not work on this system")) {
System.out.println("Test was not run because MAP_JIT write protection does not work on this system");
} else {
var pattern = Pattern.compile("Healing WXMode WXArmedForWrite at 0x[0-9a-f]* to WXWrite ");
var matches = pattern.matcher(output).results().count();
if (matches < 10) {
throw new RuntimeException("Only " + matches + " healings in\n" + output);
}
}
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2025 IBM Corporation. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.*;
import jdk.jshell.tool.*;
public class WXHealing {
// There's nothing special about jshell here: we just need an
// application that does a lot of compilation and class loading.
public static void main(String[] args) throws Throwable {
JavaShellToolBuilder
.builder()
.in(new ByteArrayInputStream
("""
void main() {
System.out.println("Hello, World!");
}
main()
2+2
Math.sqrt(2)
4 * Math.atan(1)
Math.exp(1)
"""
.getBytes()), null)
.start();
}
}