From 77e8469fb0a67d4a795f049acee7e67eaedfb5b7 Mon Sep 17 00:00:00 2001 From: Andrew Haley Date: Fri, 6 Feb 2026 13:50:54 +0000 Subject: [PATCH] 8328306: AArch64: MacOS lazy JIT "write xor execute" switching Co-authored-by: Dean Long Reviewed-by: dlong, adinn --- src/hotspot/cpu/aarch64/assembler_aarch64.hpp | 1 + .../gc/shared/barrierSetNMethod_aarch64.cpp | 4 + .../cpu/aarch64/macroAssembler_aarch64.cpp | 5 + .../cpu/aarch64/nativeInst_aarch64.cpp | 4 +- .../cpu/aarch64/nativeInst_aarch64.hpp | 21 +++-- .../cpu/aarch64/stubGenerator_aarch64.cpp | 4 +- .../cpu/aarch64/vm_version_aarch64.cpp | 16 ++++ src/hotspot/os/bsd/globals_bsd.hpp | 19 +++- src/hotspot/os/bsd/os_bsd.cpp | 1 + .../os_cpu/bsd_aarch64/os_bsd_aarch64.cpp | 91 +++++++++++++++++-- src/hotspot/share/asm/codeBuffer.cpp | 2 + src/hotspot/share/c1/c1_Runtime1.cpp | 1 + src/hotspot/share/classfile/classLoader.cpp | 2 + src/hotspot/share/classfile/classLoader.hpp | 4 + src/hotspot/share/code/codeBlob.cpp | 2 + src/hotspot/share/code/nmethod.cpp | 8 ++ src/hotspot/share/code/vtableStubs.cpp | 3 + .../share/gc/shared/barrierSetNMethod.cpp | 2 + src/hotspot/share/memory/heap.cpp | 6 ++ src/hotspot/share/opto/runtime.cpp | 2 + src/hotspot/share/prims/upcallStubs.cpp | 1 + src/hotspot/share/runtime/deoptimization.cpp | 1 + .../share/runtime/interfaceSupport.inline.hpp | 31 +++++-- src/hotspot/share/runtime/javaCalls.cpp | 29 +++--- src/hotspot/share/runtime/javaThread.cpp | 14 +-- src/hotspot/share/runtime/javaThread.hpp | 10 ++ src/hotspot/share/runtime/os.hpp | 25 ++++- src/hotspot/share/runtime/thread.hpp | 9 +- src/hotspot/share/runtime/thread.inline.hpp | 20 +++- .../share/runtime/threadWXSetters.inline.hpp | 55 +++++++++-- .../share/utilities/forbiddenFunctions.hpp | 4 + src/hotspot/share/utilities/macros.hpp | 3 + .../utilities/permitForbiddenFunctions.hpp | 4 + .../jtreg/runtime/os/TestWXHealing.java | 58 ++++++++++++ test/hotspot/jtreg/runtime/os/WXHealing.java | 48 ++++++++++ 35 files changed, 436 insertions(+), 74 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/os/TestWXHealing.java create mode 100644 test/hotspot/jtreg/runtime/os/WXHealing.java diff --git a/src/hotspot/cpu/aarch64/assembler_aarch64.hpp b/src/hotspot/cpu/aarch64/assembler_aarch64.hpp index fc6e58b801c..19b3bb1a65b 100644 --- a/src/hotspot/cpu/aarch64/assembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/assembler_aarch64.hpp @@ -4329,6 +4329,7 @@ public: #undef INSN Assembler(CodeBuffer* code) : AbstractAssembler(code) { + MACOS_AARCH64_ONLY(os::thread_wx_enable_write()); } // Stack overflow checking diff --git a/src/hotspot/cpu/aarch64/gc/shared/barrierSetNMethod_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/shared/barrierSetNMethod_aarch64.cpp index 4d5ca01b6b4..3d5261c31d1 100644 --- a/src/hotspot/cpu/aarch64/gc/shared/barrierSetNMethod_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/shared/barrierSetNMethod_aarch64.cpp @@ -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); } diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index ba42602ddc1..409343b6b8d 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -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::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; diff --git a/src/hotspot/cpu/aarch64/nativeInst_aarch64.cpp b/src/hotspot/cpu/aarch64/nativeInst_aarch64.cpp index f2003dd9b55..0cdf36f0bc5 100644 --- a/src/hotspot/cpu/aarch64/nativeInst_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/nativeInst_aarch64.cpp @@ -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(); diff --git a/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp b/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp index c30cb911d96..15b6c9ff215 100644 --- a/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp @@ -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) { diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp index db653bcf236..a459a28b09e 100644 --- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp @@ -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 } diff --git a/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp b/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp index 3fa85f8f47d..0a7bc5a8962 100644 --- a/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp @@ -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); diff --git a/src/hotspot/os/bsd/globals_bsd.hpp b/src/hotspot/os/bsd/globals_bsd.hpp index 850d491a11f..22f587ed789 100644 --- a/src/hotspot/os/bsd/globals_bsd.hpp +++ b/src/hotspot/os/bsd/globals_bsd.hpp @@ -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 diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index 0e21c2d1785..81320b4f1aa 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -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; } diff --git a/src/hotspot/os_cpu/bsd_aarch64/os_bsd_aarch64.cpp b/src/hotspot/os_cpu/bsd_aarch64/os_bsd_aarch64.cpp index 62dba218b2f..36599594842 100644 --- a/src/hotspot/os_cpu/bsd_aarch64/os_bsd_aarch64.cpp +++ b/src/hotspot/os_cpu/bsd_aarch64/os_bsd_aarch64.cpp @@ -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 @@ -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; diff --git a/src/hotspot/share/asm/codeBuffer.cpp b/src/hotspot/share/asm/codeBuffer.cpp index 7871134e923..d94f52c18f6 100644 --- a/src/hotspot/share/asm/codeBuffer.cpp +++ b/src/hotspot/share/asm/codeBuffer.cpp @@ -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(); diff --git a/src/hotspot/share/c1/c1_Runtime1.cpp b/src/hotspot/share/c1/c1_Runtime1.cpp index a4c956ff5be..63764dd113a 100644 --- a/src/hotspot/share/c1/c1_Runtime1.cpp +++ b/src/hotspot/share/c1/c1_Runtime1.cpp @@ -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, diff --git a/src/hotspot/share/classfile/classLoader.cpp b/src/hotspot/share/classfile/classLoader.cpp index f631bfaa102..eced83577cb 100644 --- a/src/hotspot/share/classfile/classLoader.cpp +++ b/src/hotspot/share/classfile/classLoader.cpp @@ -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"); diff --git a/src/hotspot/share/classfile/classLoader.hpp b/src/hotspot/share/classfile/classLoader.hpp index afb0a581dcc..a935d3027ac 100644 --- a/src/hotspot/share/classfile/classLoader.hpp +++ b/src/hotspot/share/classfile/classLoader.hpp @@ -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; } diff --git a/src/hotspot/share/code/codeBlob.cpp b/src/hotspot/share/code/codeBlob.cpp index a0a34ec23fa..094b4f82cf0 100644 --- a/src/hotspot/share/code/codeBlob.cpp +++ b/src/hotspot/share/code/codeBlob.cpp @@ -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(); } diff --git a/src/hotspot/share/code/nmethod.cpp b/src/hotspot/share/code/nmethod.cpp index edfca5c98ee..13eb1ff1604 100644 --- a/src/hotspot/share/code/nmethod.cpp +++ b/src/hotspot/share/code/nmethod.cpp @@ -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 diff --git a/src/hotspot/share/code/vtableStubs.cpp b/src/hotspot/share/code/vtableStubs.cpp index b926888595d..35b226a8798 100644 --- a/src/hotspot/share/code/vtableStubs.cpp +++ b/src/hotspot/share/code/vtableStubs.cpp @@ -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 diff --git a/src/hotspot/share/gc/shared/barrierSetNMethod.cpp b/src/hotspot/share/gc/shared/barrierSetNMethod.cpp index ab94bae079a..a1f03a4bf50 100644 --- a/src/hotspot/share/gc/shared/barrierSetNMethod.cpp +++ b/src/hotspot/share/gc/shared/barrierSetNMethod.cpp @@ -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 diff --git a/src/hotspot/share/memory/heap.cpp b/src/hotspot/share/memory/heap.cpp index a1333ed13e9..3ade5169eef 100644 --- a/src/hotspot/share/memory/heap.cpp +++ b/src/hotspot/share/memory/heap.cpp @@ -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); diff --git a/src/hotspot/share/opto/runtime.cpp b/src/hotspot/share/opto/runtime.cpp index 6dbbfb0a130..0d2dbb813bd 100644 --- a/src/hotspot/share/opto/runtime.cpp +++ b/src/hotspot/share/opto/runtime.cpp @@ -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 diff --git a/src/hotspot/share/prims/upcallStubs.cpp b/src/hotspot/share/prims/upcallStubs.cpp index 5215e6d1735..a3271589fbb 100644 --- a/src/hotspot/share/prims/upcallStubs.cpp +++ b/src/hotspot/share/prims/upcallStubs.cpp @@ -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) { diff --git a/src/hotspot/share/runtime/deoptimization.cpp b/src/hotspot/share/runtime/deoptimization.cpp index e2029a26d37..2beba9abb06 100644 --- a/src/hotspot/share/runtime/deoptimization.cpp +++ b/src/hotspot/share/runtime/deoptimization.cpp @@ -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); diff --git a/src/hotspot/share/runtime/interfaceSupport.inline.hpp b/src/hotspot/share/runtime/interfaceSupport.inline.hpp index ecd7397d81a..cd3a45a8d3c 100644 --- a/src/hotspot/share/runtime/interfaceSupport.inline.hpp +++ b/src/hotspot/share/runtime/interfaceSupport.inline.hpp @@ -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) diff --git a/src/hotspot/share/runtime/javaCalls.cpp b/src/hotspot/share/runtime/javaCalls.cpp index 3ed678284e4..da701640006 100644 --- a/src/hotspot/share/runtime/javaCalls.cpp +++ b/src/hotspot/share/runtime/javaCalls.cpp @@ -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 diff --git a/src/hotspot/share/runtime/javaThread.cpp b/src/hotspot/share/runtime/javaThread.cpp index e73347f35d8..a891e333d4c 100644 --- a/src/hotspot/share/runtime/javaThread.cpp +++ b/src/hotspot/share/runtime/javaThread.cpp @@ -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()); diff --git a/src/hotspot/share/runtime/javaThread.hpp b/src/hotspot/share/runtime/javaThread.hpp index 1aae37c0697..755a6abe0d8 100644 --- a/src/hotspot/share/runtime/javaThread.hpp +++ b/src/hotspot/share/runtime/javaThread.hpp @@ -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; diff --git a/src/hotspot/share/runtime/os.hpp b/src/hotspot/share/runtime/os.hpp index 29c872157fd..c6a1d670926 100644 --- a/src/hotspot/share/runtime/os.hpp +++ b/src/hotspot/share/runtime/os.hpp @@ -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: diff --git a/src/hotspot/share/runtime/thread.hpp b/src/hotspot/share/runtime/thread.hpp index 29e3c07ba20..dc09d4c68c2 100644 --- a/src/hotspot/share/runtime/thread.hpp +++ b/src/hotspot/share/runtime/thread.hpp @@ -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; diff --git a/src/hotspot/share/runtime/thread.inline.hpp b/src/hotspot/share/runtime/thread.inline.hpp index 61866674224..b194f5e2a7f 100644 --- a/src/hotspot/share/runtime/thread.inline.hpp +++ b/src/hotspot/share/runtime/thread.inline.hpp @@ -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 diff --git a/src/hotspot/share/runtime/threadWXSetters.inline.hpp b/src/hotspot/share/runtime/threadWXSetters.inline.hpp index 121584b81be..e7e37fcde1b 100644 --- a/src/hotspot/share/runtime/threadWXSetters.inline.hpp +++ b/src/hotspot/share/runtime/threadWXSetters.inline.hpp @@ -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 diff --git a/src/hotspot/share/utilities/forbiddenFunctions.hpp b/src/hotspot/share/utilities/forbiddenFunctions.hpp index 9d1b88e6233..9b3c65b0656 100644 --- a/src/hotspot/share/utilities/forbiddenFunctions.hpp +++ b/src/hotspot/share/utilities/forbiddenFunctions.hpp @@ -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 diff --git a/src/hotspot/share/utilities/macros.hpp b/src/hotspot/share/utilities/macros.hpp index 8404fc757f0..a03255b5cf3 100644 --- a/src/hotspot/share/utilities/macros.hpp +++ b/src/hotspot/share/utilities/macros.hpp @@ -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 diff --git a/src/hotspot/share/utilities/permitForbiddenFunctions.hpp b/src/hotspot/share/utilities/permitForbiddenFunctions.hpp index 71719ac8a76..5dec5062b0c 100644 --- a/src/hotspot/share/utilities/permitForbiddenFunctions.hpp +++ b/src/hotspot/share/utilities/permitForbiddenFunctions.hpp @@ -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 diff --git a/test/hotspot/jtreg/runtime/os/TestWXHealing.java b/test/hotspot/jtreg/runtime/os/TestWXHealing.java new file mode 100644 index 00000000000..46875848a89 --- /dev/null +++ b/test/hotspot/jtreg/runtime/os/TestWXHealing.java @@ -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); + } + } + } +} diff --git a/test/hotspot/jtreg/runtime/os/WXHealing.java b/test/hotspot/jtreg/runtime/os/WXHealing.java new file mode 100644 index 00000000000..ba790db2c59 --- /dev/null +++ b/test/hotspot/jtreg/runtime/os/WXHealing.java @@ -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(); + } +}