diff --git a/make/autoconf/flags-cflags.m4 b/make/autoconf/flags-cflags.m4 index e80d9a98957..38e8f2fce32 100644 --- a/make/autoconf/flags-cflags.m4 +++ b/make/autoconf/flags-cflags.m4 @@ -683,6 +683,9 @@ AC_DEFUN([FLAGS_SETUP_CFLAGS_CPU_DEP], $1_DEFINES_CPU_JDK="${$1_DEFINES_CPU_JDK} -DARCH='\"$FLAGS_CPU_LEGACY\"' \ -D$FLAGS_CPU_LEGACY" + # setup arch name (the same as 'os.arch' system property) + $1_DEFINES_CPU_JVM="${$1_DEFINES_CPU_JVM} -DARCHPROPNAME='\"$OPENJDK_TARGET_CPU_OSARCH\"'" + if test "x$FLAGS_CPU_BITS" = x64; then $1_DEFINES_CPU_JDK="${$1_DEFINES_CPU_JDK} -D_LP64=1" $1_DEFINES_CPU_JVM="${$1_DEFINES_CPU_JVM} -D_LP64=1" diff --git a/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp b/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp index bca985e0051..0c6c1c603be 100644 --- a/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp @@ -64,10 +64,11 @@ class VM_Version : public Abstract_VM_Version { public: // Initialization static void initialize(); - struct VM_Features {}; + struct VM_Features { + const char *print_numbers() const { return ""; } + }; static bool cpu_features_binary(VM_Features *data) { return false; } - static bool cpu_features_binary_check(const VM_Features *data) { return data == nullptr; } - static bool ignore_cpu_features() { return true; } + static bool ignore_cpu_features(bool is_checkpoint) { return true; } static void check_virtualizations(); static void print_platform_virtualization_info(outputStream*); diff --git a/src/hotspot/cpu/arm/vm_version_arm.hpp b/src/hotspot/cpu/arm/vm_version_arm.hpp index 99e2f570a91..1b6b63cce78 100644 --- a/src/hotspot/cpu/arm/vm_version_arm.hpp +++ b/src/hotspot/cpu/arm/vm_version_arm.hpp @@ -41,10 +41,11 @@ class VM_Version: public Abstract_VM_Version { public: static void initialize(); static bool is_initialized() { return _is_initialized; } - struct VM_Features {}; + struct VM_Features { + const char *print_numbers() const { return ""; } + }; static bool cpu_features_binary(VM_Features *data) { return false; } - static bool cpu_features_binary_check(const VM_Features *data) { return data == nullptr; } - static bool ignore_cpu_features() { return true; } + static bool ignore_cpu_features(bool is_checkpoint) { return true; } protected: diff --git a/src/hotspot/cpu/ppc/vm_version_ppc.hpp b/src/hotspot/cpu/ppc/vm_version_ppc.hpp index e69ad8a0e3f..fa216c65078 100644 --- a/src/hotspot/cpu/ppc/vm_version_ppc.hpp +++ b/src/hotspot/cpu/ppc/vm_version_ppc.hpp @@ -53,10 +53,11 @@ class VM_Version: public Abstract_VM_Version { // Initialization static void initialize(); static void check_virtualizations(); - struct VM_Features {}; + struct VM_Features { + const char *print_numbers() const { return ""; } + }; static bool cpu_features_binary(VM_Features *data) { return false; } - static bool cpu_features_binary_check(const VM_Features *data) { return data == nullptr; } - static bool ignore_cpu_features() { return true; } + static bool ignore_cpu_features(bool is_checkpoint) { return true; } // Override Abstract_VM_Version implementation static void print_platform_virtualization_info(outputStream*); diff --git a/src/hotspot/cpu/riscv/vm_version_riscv.hpp b/src/hotspot/cpu/riscv/vm_version_riscv.hpp index 3f9b8390498..59b0a264af9 100644 --- a/src/hotspot/cpu/riscv/vm_version_riscv.hpp +++ b/src/hotspot/cpu/riscv/vm_version_riscv.hpp @@ -307,10 +307,11 @@ class VM_Version : public Abstract_VM_Version { // Initialization static void initialize(); static void initialize_cpu_information(); - struct VM_Features {}; + struct VM_Features { + const char *print_numbers() const { return ""; } + }; static bool cpu_features_binary(VM_Features *data) { return false; } - static bool cpu_features_binary_check(const VM_Features *data) { return data == nullptr; } - static bool ignore_cpu_features() { return true; } + static bool ignore_cpu_features(bool is_checkpoint) { return true; } constexpr static bool supports_stack_watermark_barrier() { return true; } diff --git a/src/hotspot/cpu/s390/vm_version_s390.hpp b/src/hotspot/cpu/s390/vm_version_s390.hpp index 3b5b4cf5898..8b56ea17641 100644 --- a/src/hotspot/cpu/s390/vm_version_s390.hpp +++ b/src/hotspot/cpu/s390/vm_version_s390.hpp @@ -414,10 +414,11 @@ class VM_Version: public Abstract_VM_Version { static void initialize(); static void print_features(); static bool is_determine_features_test_running() { return _is_determine_features_test_running; } - struct VM_Features {}; + struct VM_Features { + const char *print_numbers() const { return ""; } + }; static bool cpu_features_binary(VM_Features *data) { return false; } - static bool cpu_features_binary_check(const VM_Features *data) { return data == nullptr; } - static bool ignore_cpu_features() { return true; } + static bool ignore_cpu_features(bool is_checkpoint) { return true; } // Override Abstract_VM_Version implementation static void print_platform_virtualization_info(outputStream*); diff --git a/src/hotspot/cpu/x86/vm_version_x86.cpp b/src/hotspot/cpu/x86/vm_version_x86.cpp index 1fcd28675b0..fcef5705dc0 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.cpp +++ b/src/hotspot/cpu/x86/vm_version_x86.cpp @@ -113,6 +113,12 @@ bool VM_Version::supports_clflush() { return false; } +const char *VM_Version::VM_Features::print_numbers() const { + char *buf = NEW_RESOURCE_ARRAY(char, MAX_CPU_FEATURES); + print_numbers(buf, MAX_CPU_FEATURES); + return buf; +} + #define CPUID_STANDARD_FN 0x0 #define CPUID_STANDARD_FN_1 0x1 #define CPUID_STANDARD_FN_4 0x4 @@ -2557,55 +2563,8 @@ void VM_Version::VM_Features::print_missing_features() const { bool VM_Version::cpu_features_binary(VM_Version::VM_Features *data) { *data = _features; - return true; -} - -bool VM_Version::cpu_features_binary_check(const VM_Version::VM_Features *data_ptr) { - assert(CPUFeatures == nullptr, "This should only be called on restore and CPUFeatures is not restore-settable"); - - if (!data_ptr) { - return false; - } - VM_Version::VM_Features data = *data_ptr; - - if (ShowCPUFeatures) { - char buf[MAX_CPU_FEATURES * 16]; - data.print_numbers_and_names(buf, sizeof(buf)); - tty->print_cr("This snapshot's stored CPU features are: -XX:CPUFeatures=%s", buf); - } - - VM_Version::VM_Features features_missing = data & ~_features; - // Workaround JDK-8311164: CPU_HT is set randomly on hybrid CPUs like Alder Lake. - features_missing.clear_feature(CPU_HT); - - if (!features_missing.empty()) { - char buf_use[MAX_CPU_FEATURES]; - (data & _features).print_numbers(buf_use, sizeof(buf_use)); - char buf_have[MAX_CPU_FEATURES]; - data.print_numbers(buf_have, sizeof(buf_have)); - tty->print("You have to specify -XX:CPUFeatures=%s together with -XX:CRaCCheckpointTo when making a checkpoint file" - "; specified -XX:CRaCRestoreFrom file contains CPU features %s", - buf_use, buf_have); - features_missing.print_missing_features(); - if (!IgnoreCPUFeatures) { - return false; - } - } - - _features_saved = _features; - _features = data; - - if (ShowCPUFeatures && !CRaCRestoreFrom) { - print_using_features_cr(); - } - -#ifdef LINUX - // glibc_not_using() has done setenv(TUNABLES_NAME) and it expects us to re-exec ourselves. - // But we were only checking the cpufeatures file before restoring the process so we ignore the result. - glibc_not_using(); -#endif - + data->clear_feature(CPU_HT); return true; } diff --git a/src/hotspot/cpu/x86/vm_version_x86.hpp b/src/hotspot/cpu/x86/vm_version_x86.hpp index 1b3e99b37a7..59f60a5bfd7 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.hpp +++ b/src/hotspot/cpu/x86/vm_version_x86.hpp @@ -612,6 +612,8 @@ class VM_Version : public Abstract_VM_Version { return buf - buf_orig; } + const char *print_numbers() const; + void print_numbers_and_names(char *buf, size_t buflen) const { int res = print_numbers(buf, buflen); buf += res; @@ -908,8 +910,12 @@ class VM_Version : public Abstract_VM_Version { // Initialization static void initialize(); static bool cpu_features_binary(VM_Features *data); - static bool cpu_features_binary_check(const VM_Features *data); - static bool ignore_cpu_features() { return _ignore_glibc_not_using; } + static bool ignore_cpu_features(bool is_checkpoint) { + // This gets triggered by -XX:CPUFeatures=ignore, not writing the features & arch + // on checkpoint into the image at all, and skipping the check on restore. + // IgnoreCPUFeatures is ignored on checkpoint + return _ignore_glibc_not_using || (!is_checkpoint && IgnoreCPUFeatures); + } static void restore_check(const char* str, const char* msg_prefix); // Override Abstract_VM_Version implementation diff --git a/src/hotspot/cpu/zero/vm_version_zero.hpp b/src/hotspot/cpu/zero/vm_version_zero.hpp index 3b840961e37..74d447b7976 100644 --- a/src/hotspot/cpu/zero/vm_version_zero.hpp +++ b/src/hotspot/cpu/zero/vm_version_zero.hpp @@ -32,10 +32,11 @@ class VM_Version : public Abstract_VM_Version { public: static void initialize(); - struct VM_Features {}; + struct VM_Features { + const char *print_numbers() const { return ""; } + }; static bool cpu_features_binary(VM_Features *data) { return false; } - static bool cpu_features_binary_check(const VM_Features *data) { return data == nullptr; } - static bool ignore_cpu_features() { return true; } + static bool ignore_cpu_features(bool is_checkpoint) { return true; } constexpr static bool supports_stack_watermark_barrier() { return true; } diff --git a/src/hotspot/share/include/crlib/crlib_image_constraints.h b/src/hotspot/share/include/crlib/crlib_image_constraints.h new file mode 100644 index 00000000000..2b5988bd45b --- /dev/null +++ b/src/hotspot/share/include/crlib/crlib_image_constraints.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2025, Azul Systems, Inc. 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. + */ +#ifndef CRLIB_IMAGE_CONSTRAINTS_H +#define CRLIB_IMAGE_CONSTRAINTS_H + +#include "crlib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CRLIB_EXTENSION_IMAGE_CONSTRAINTS_NAME "image constraints" +#define CRLIB_EXTENSION_IMAGE_CONSTRAINTS(api) \ + CRLIB_EXTENSION(api, crlib_image_constraints_t, CRLIB_EXTENSION_IMAGE_CONSTRAINTS_NAME) + +// When two bitmaps of different size are compared this behaves as if the shorter +// bitmap was extended with zeros to the length of the longer bitmap. +typedef enum { + EQUALS, + // Bitmap in image must be subset or equal to bitmap in constraint + SUBSET, + // Bitmap in image must be superset or equal to bitmap in constraint + SUPERSET, +} crlib_bitmap_comparison_t; + +// API for storing & verifying application-defined image characteristics, generally called tags. +typedef const struct crlib_image_constraints { + crlib_extension_t header; + + // Invoked before checkpoint. Return false if name or value exceed limits, or if the name has already been used. + bool (*set_label)(crlib_conf_t *, const char *name, const char *value); + bool (*set_bitmap)(crlib_conf_t *, const char *name, const unsigned char *value, size_t size_bytes); + + // Invoked before restore. The conditions are not evaluated immediately; the restore will fail + // if these constraints are not matched. + // Multiple constraints on the same tag are permitted. + // These methods return false when the constraint cannot be added to the configuration. + bool (*require_label)(crlib_conf_t *, const char *name, const char *value); + bool (*require_bitmap)(crlib_conf_t *, const char *name, const unsigned char *value, size_t size_bytes, crlib_bitmap_comparison_t comparison); + + // Invoked after (failed) restore. Returns true if the restore failed due to + // any of the constraints on tag , false otherwise. + bool (*is_failed)(crlib_conf_t *, const char *name); +} crlib_image_constraints_t; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // CRLIB_IMAGE_CONSTRAINTS_H diff --git a/src/hotspot/share/runtime/crac.cpp b/src/hotspot/share/runtime/crac.cpp index 13badc0d314..2fb8e722f43 100644 --- a/src/hotspot/share/runtime/crac.cpp +++ b/src/hotspot/share/runtime/crac.cpp @@ -56,6 +56,7 @@ static jlong _restore_start_time; static jlong _restore_start_nanos; CracEngine *crac::_engine = nullptr; +unsigned int crac::_generation = 1; char crac::_checkpoint_bootid[UUID_LENGTH]; jlong crac::_checkpoint_wallclock_seconds; jlong crac::_checkpoint_wallclock_nanos; @@ -102,23 +103,27 @@ static char * strchrnul(char * str, char c) { int crac::checkpoint_restore(int *shmid) { guarantee(_engine != nullptr, "CRaC engine is not initialized"); + // If this is a second checkpoint we should use a clear configuration + if (_generation != 0 && !_engine->reset_conf()) { + return JVM_CHECKPOINT_ERROR; + } + crac::record_time_before_checkpoint(); // CRaCCheckpointTo can be changed on restore so we need to update the conf // to account for that. // Note that CRaCEngine and CRaCEngineOptions are not updated (as documented) // so we don't need to re-init the whole engine handle. - if (restore_start_time() != -1 && // A way to detect we've restored at least once - !_engine->configure_image_location(CRaCCheckpointTo)) { + if (_generation != 0 && !_engine->configure_image_location(CRaCCheckpointTo)) { return JVM_CHECKPOINT_ERROR; } - if (!VM_Version::ignore_cpu_features()) { + if (!VM_Version::ignore_cpu_features(true)) { VM_Version::VM_Features data; if (VM_Version::cpu_features_binary(&data)) { - switch (_engine->prepare_user_data_api()) { + switch (_engine->prepare_image_constraints_api()) { case CracEngine::ApiStatus::OK: - if (!_engine->cpufeatures_store(&data)) { + if (!_engine->store_cpuinfo(&data)) { return JVM_CHECKPOINT_ERROR; } break; @@ -275,6 +280,7 @@ void VM_Crac::doit() { } } + crac::_generation++; Arguments::reset_for_crac_restore(); os::reset_cached_process_id(); @@ -520,17 +526,16 @@ void crac::restore(crac_restore_data& restore_data) { return; } - if (!VM_Version::ignore_cpu_features()) { - switch (engine.prepare_user_data_api()) { + // Previously IgnoreCPUFeatures didn't disable the check completely; the difference + // was printed out but continued even despite features not being satisfied. + // Since the check itself is delegated to the C/R Engine we will simply + // skip the check here. + if (!VM_Version::ignore_cpu_features(false)) { + switch (engine.prepare_image_constraints_api()) { case CracEngine::ApiStatus::OK: { VM_Version::VM_Features data; - bool present; - if (!engine.cpufeatures_load(&data, &present)) { - return; - } - if (!VM_Version::cpu_features_binary_check(present ? &data : nullptr)) { - log_error(crac)("Image %s has incompatible CPU features in its user data", CRaCRestoreFrom); - return; + if (VM_Version::cpu_features_binary(&data)) { + engine.require_cpuinfo(&data); } } break; case CracEngine::ApiStatus::ERR: @@ -580,7 +585,10 @@ void crac::restore(crac_restore_data& restore_data) { const int ret = engine.restore(); if (ret != 0) { - log_error(crac)("CRaC engine failed to restore from %s: error %i", CRaCRestoreFrom, ret); + log_error(crac)("CRaC engine failed to restore from %s", CRaCRestoreFrom); + VM_Version::VM_Features data; + VM_Version::cpu_features_binary(&data); // ignore return value + engine.check_cpuinfo(&data); } } diff --git a/src/hotspot/share/runtime/crac.hpp b/src/hotspot/share/runtime/crac.hpp index 1c607f3a50e..e1ae9fcf95a 100644 --- a/src/hotspot/share/runtime/crac.hpp +++ b/src/hotspot/share/runtime/crac.hpp @@ -59,6 +59,7 @@ class crac: AllStatic { private: static CracEngine *_engine; + static unsigned int _generation; static char _checkpoint_bootid[UUID_LENGTH]; // Timestamps recorded before checkpoint. diff --git a/src/hotspot/share/runtime/crac_engine.cpp b/src/hotspot/share/runtime/crac_engine.cpp index 056e5607615..771508a36d7 100644 --- a/src/hotspot/share/runtime/crac_engine.cpp +++ b/src/hotspot/share/runtime/crac_engine.cpp @@ -23,9 +23,9 @@ #include "crlib/crlib.h" #include "crlib/crlib_restore_data.h" -#include "crlib/crlib_user_data.h" #include "logging/log.hpp" #include "memory/allStatic.hpp" +#include "memory/resourceArea.hpp" #include "nmt/memTag.hpp" #include "runtime/crac_engine.hpp" #include "runtime/globals.hpp" @@ -277,9 +277,8 @@ CracEngine::CracEngine(const char *image_location) { bool is_static_crexec = false; // true when using statically linked crexec - char exec_path[JVM_MAXPATHLEN] = "\0"; if (!is_library) { - strcpy(exec_path, path); // Save to later pass it to crexec + _exec_path = os::strdup_check_oom(path); // Save to later pass it to crexec if (is_vm_statically_linked()) { is_static_crexec = true; os::jvm_path(path, sizeof(path)); // points to bin/java for static JDK @@ -320,8 +319,7 @@ CracEngine::CracEngine(const char *image_location) { return; } - const char *exec_location = exec_path[0] != '\0' ? exec_path : nullptr; - crlib_conf_t * const conf = create_conf(*api, image_location, exec_location); + crlib_conf_t * const conf = create_conf(*api, image_location, _exec_path); if (conf == nullptr) { os::dll_unload(lib); return; @@ -333,12 +331,19 @@ CracEngine::CracEngine(const char *image_location) { } CracEngine::~CracEngine() { + os::free(_exec_path); if (is_initialized()) { _api->destroy_conf(_conf); os::dll_unload(_lib); } } +bool CracEngine::reset_conf() { + _api->destroy_conf(_conf); + _conf = create_conf(*_api, nullptr, _exec_path); + return _conf != nullptr; +} + bool CracEngine::is_initialized() const { assert((_lib == nullptr && _api == nullptr && _conf == nullptr) || (_lib != nullptr && _api != nullptr && _conf != nullptr), "invariant"); @@ -432,64 +437,60 @@ const char *CracEngine::configuration_doc() const { return _description_api->configuration_doc(_conf); } -static constexpr char cpufeatures_userdata_name[] = "cpufeatures"; +static constexpr char cpuarch_name[] = "cpu.arch"; +static constexpr char cpufeatures_name[] = "cpu.features"; -CracEngine::ApiStatus CracEngine::prepare_user_data_api() { +CracEngine::ApiStatus CracEngine::prepare_image_constraints_api() { precond(is_initialized()); - if (_user_data_api != nullptr) { + if (_image_constraints_api != nullptr) { return ApiStatus::OK; } - crlib_user_data_t * const user_data_api = CRLIB_EXTENSION_USER_DATA(_api); - if (user_data_api == nullptr) { - log_debug(crac)("CRaC engine does not support extension: " CRLIB_EXTENSION_USER_DATA_NAME); + crlib_image_constraints_t * const ic_api = CRLIB_EXTENSION_IMAGE_CONSTRAINTS(_api); + if (ic_api == nullptr) { + log_debug(crac)("CRaC engine does not support extension: " CRLIB_EXTENSION_IMAGE_CONSTRAINTS_NAME); return ApiStatus::UNSUPPORTED; } - if (user_data_api->set_user_data == nullptr || user_data_api->load_user_data == nullptr - || user_data_api->lookup_user_data == nullptr || user_data_api->destroy_user_data == nullptr) { - log_error(crac)("CRaC engine provided invalid API for extension: " CRLIB_EXTENSION_USER_DATA_NAME); + if (ic_api->set_label == nullptr || ic_api->set_bitmap == nullptr + || ic_api->require_label == nullptr || ic_api->require_bitmap == nullptr) { + log_error(crac)("CRaC engine provided invalid API for extension: " CRLIB_EXTENSION_IMAGE_CONSTRAINTS_NAME); return ApiStatus::ERR; } - _user_data_api = user_data_api; + _image_constraints_api = ic_api; return ApiStatus::OK; } // Return success. -bool CracEngine::cpufeatures_store(const VM_Version::VM_Features *datap) const { - log_debug(crac)("cpufeatures_store user data %s to %s...", cpufeatures_userdata_name, CRaCRestoreFrom); - const bool ok = _user_data_api->set_user_data(_conf, cpufeatures_userdata_name, datap, sizeof(*datap)); - if (!ok) { - log_error(crac)("CRaC engine failed to store user data %s", cpufeatures_userdata_name); +bool CracEngine::store_cpuinfo(const VM_Version::VM_Features *datap) const { + log_debug(crac)("store cpu.arch & cpu.features to %s...", CRaCRestoreFrom); + if (!_image_constraints_api->set_label(_conf, cpuarch_name, ARCHPROPNAME)) { + log_error(crac)("CRaC engine failed to record label %s", cpuarch_name); + return false; + } + if (!_image_constraints_api->set_bitmap(_conf, cpufeatures_name, reinterpret_cast(datap), sizeof(*datap))) { + log_error(crac)("CRaC engine failed to record bitmap %s", cpufeatures_name); + return false; } - return ok; + return true; } -// Return success. -bool CracEngine::cpufeatures_load(VM_Version::VM_Features *datap, bool *presentp) const { - log_debug(crac)("cpufeatures_load user data %s from %s...", cpufeatures_userdata_name, CRaCRestoreFrom); - crlib_user_data_storage_t *user_data; - if (!(user_data = _user_data_api->load_user_data(_conf))) { - log_error(crac)("CRaC engine failed to load user data %s", cpufeatures_userdata_name); - return false; +void CracEngine::require_cpuinfo(const VM_Version::VM_Features *datap) const { + log_debug(crac)("cpufeatures_load user data %s from %s...", cpufeatures_name, CRaCRestoreFrom); + _image_constraints_api->require_label(_conf, cpuarch_name, ARCHPROPNAME); + _image_constraints_api->require_bitmap(_conf, cpufeatures_name, + reinterpret_cast(datap), sizeof(*datap), SUBSET); +} + +void CracEngine::check_cpuinfo(const VM_Version::VM_Features *datap) const { + if (_image_constraints_api == nullptr) { + // When CPU features are ignored + return; } - const VM_Version::VM_Features *cdatap; - size_t size; - if (_user_data_api->lookup_user_data(user_data, cpufeatures_userdata_name, (const void **) &cdatap, &size)) { - if (size != sizeof(VM_Version::VM_Features)) { - _user_data_api->destroy_user_data(user_data); - log_error(crac)("User data %s in %s has unexpected size %zu (expected %zu)", cpufeatures_userdata_name, CRaCRestoreFrom, size, sizeof(VM_Version::VM_Features)); - return false; - } - if (cdatap == nullptr) { - _user_data_api->destroy_user_data(user_data); - log_error(crac)("lookup_user_data %s should return non-null data pointer", cpufeatures_userdata_name); - return false; - } - *datap = *cdatap; - *presentp = true; - } else { - *presentp = false; + if (_image_constraints_api->is_failed(_conf, cpuarch_name)) { + log_error(crac)("Restore failed due to wrong or missing CPU architecture (current architecture is " ARCHPROPNAME ")"); + } + if (_image_constraints_api->is_failed(_conf, cpufeatures_name)) { + ResourceMark rm; + log_error(crac)("Restore failed due to incompatible or missing CPU features, try using -XX:CPUFeatures=%s on checkpoint.", datap->print_numbers()); } - _user_data_api->destroy_user_data(user_data); - return true; } diff --git a/src/hotspot/share/runtime/crac_engine.hpp b/src/hotspot/share/runtime/crac_engine.hpp index 750d4b328f9..e2912b399f3 100644 --- a/src/hotspot/share/runtime/crac_engine.hpp +++ b/src/hotspot/share/runtime/crac_engine.hpp @@ -26,8 +26,8 @@ #include "crlib/crlib.h" #include "crlib/crlib_description.h" +#include "crlib/crlib_image_constraints.h" #include "crlib/crlib_restore_data.h" -#include "crlib/crlib_user_data.h" #include "memory/allocation.hpp" #include "nmt/memTag.hpp" #include "runtime/vm_version.hpp" @@ -48,8 +48,10 @@ class CracEngine : public CHeapObj { // Use this to check whether the constructor succeeded. bool is_initialized() const; - // Operations supported by all engines + // Reinitialize configuration before checkpoint + bool reset_conf(); + // Operations supported by all engines int checkpoint() const; int restore() const; bool configure_image_location(const char *image_location) const; @@ -67,18 +69,21 @@ class CracEngine : public CHeapObj { const char *description() const; const char *configuration_doc() const; - ApiStatus prepare_user_data_api(); - bool cpufeatures_store(const VM_Version::VM_Features *datap) const; - bool cpufeatures_load(VM_Version::VM_Features *datap, bool *presentp) const; + ApiStatus prepare_image_constraints_api(); + bool store_cpuinfo(const VM_Version::VM_Features *datap) const; + void require_cpuinfo(const VM_Version::VM_Features *datap) const; + void check_cpuinfo(const VM_Version::VM_Features *datap) const; private: void *_lib = nullptr; crlib_api_t *_api = nullptr; crlib_conf_t *_conf = nullptr; + char *_exec_path = nullptr; + crlib_restore_data_t *_restore_data_api = nullptr; crlib_description_t *_description_api = nullptr; - crlib_user_data_t *_user_data_api = nullptr; + crlib_image_constraints_t *_image_constraints_api = nullptr; }; #endif // SHARE_RUNTIME_CRAC_ENGINE_HPP diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index 82f85209f49..db319ba9971 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -613,7 +613,7 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { if (CRaCRestoreFrom) { crac::restore(restore_data); if (!CRaCIgnoreRestoreIfUnavailable) { - log_error(crac)("Failed to restore %s", CRaCRestoreFrom); + log_error(crac)("Failed to restore from %s", CRaCRestoreFrom); return JNI_ERR; } } diff --git a/src/java.base/share/native/libcrexec/crexec.cpp b/src/java.base/share/native/libcrexec/crexec.cpp index 5e82fd08cb6..1479543be9e 100644 --- a/src/java.base/share/native/libcrexec/crexec.cpp +++ b/src/java.base/share/native/libcrexec/crexec.cpp @@ -33,9 +33,12 @@ #include "crlib/crlib.h" #include "crlib/crlib_description.h" +#include "crlib/crlib_image_constraints.h" #include "crlib/crlib_restore_data.h" #include "crlib/crlib_user_data.h" +#include "crexec.hpp" #include "hashtable.hpp" +#include "image_constraints.hpp" #include "jni.h" #ifdef LINUX @@ -44,10 +47,6 @@ #include "jvm.h" #endif // LINUX -#ifndef PATH_MAX -# define PATH_MAX 1024 -#endif - extern "C" { JNIEXPORT crlib_api_t *CRLIB_API(int api_version, size_t api_size); @@ -74,6 +73,12 @@ static crlib_user_data_storage_t *load_user_data(crlib_conf_t *conf); static bool lookup_user_data(crlib_user_data_storage_t *user_data, const char *name, const void **data_p, size_t *size_p); static void destroy_user_data(crlib_user_data_storage_t *user_data); +static bool set_label(crlib_conf_t *, const char *name, const char *value); +static bool set_bitmap(crlib_conf_t *, const char *name, const unsigned char *value, size_t length_bytes); +static bool require_label(crlib_conf_t *, const char *name, const char *value); +static bool require_bitmap(crlib_conf_t *, const char *name, const unsigned char *value, size_t length_bytes, crlib_bitmap_comparison_t comparison); +static bool is_failed(crlib_conf_t *, const char *name); + } // extern "C" static crlib_api_t api = { @@ -118,16 +123,26 @@ static crlib_user_data_t user_data_extension = { destroy_user_data, }; +static crlib_image_constraints_t image_constraints_extension = { + { + CRLIB_EXTENSION_IMAGE_CONSTRAINTS_NAME, + sizeof(image_constraints_extension) + }, + set_label, + set_bitmap, + require_label, + require_bitmap, + is_failed, +}; + static const crlib_extension_t *extensions[] = { &restore_data_extension.header, + &image_constraints_extension.header, &user_data_extension.header, &description_extension.header, nullptr }; -#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) - -#define CREXEC "crexec: " // crexec_md.cpp const char *file_separator(); @@ -213,6 +228,7 @@ struct crlib_conf { BoolOption _direct_map{true}; int _restore_data = 0; const char *_argv[ARGV_LAST + 2] = {}; // Last element is required to be null + ImageConstraints _image_constraints; public: crlib_conf() { @@ -283,6 +299,10 @@ struct crlib_conf { return available_size; } + ImageConstraints &image_constraints() { + return _image_constraints; + } + private: bool configure_image_location(const char *image_location) { const char *copy = strdup_checked(image_location); @@ -572,6 +592,26 @@ static void destroy_user_data(crlib_user_data_storage_t *user_data) { free(user_data); } +static bool set_label(crlib_conf_t *conf, const char *name, const char *value) { + return conf->image_constraints().set_label(name, value); +} + +static bool set_bitmap(crlib_conf_t *conf, const char *name, const unsigned char *value, size_t length_bytes) { + return conf->image_constraints().set_bitmap(name, value, length_bytes); +} + +static bool require_label(crlib_conf_t *conf, const char *name, const char *value) { + return conf->image_constraints().require_label(name, value); +} + +static bool require_bitmap(crlib_conf_t *conf, const char *name, const unsigned char *value, size_t length_bytes, crlib_bitmap_comparison_t comparison) { + return conf->image_constraints().require_bitmap(name, value, length_bytes, comparison); +} + +static bool is_failed(crlib_conf_t *conf, const char *name) { + return conf->image_constraints().is_failed(name); +} + static const crlib_extension_t *get_extension(const char *name, size_t size) { for (size_t i = 0; i < ARRAY_SIZE(extensions) - 1 /* omit nullptr */; i++) { const crlib_extension_t *ext = extensions[i]; @@ -719,6 +759,10 @@ static int checkpoint(crlib_conf_t *conf) { fprintf(stderr, CREXEC "%s has no effect on checkpoint\n", opt_direct_map); } + if (!conf->image_constraints().persist(conf->argv()[ARGV_IMAGE_LOCATION])) { + return -1; + } + { Environment env; if (!env.is_initialized() || @@ -773,6 +817,10 @@ static int restore(crlib_conf_t *conf) { fprintf(stderr, CREXEC "%s has no effect on restore\n", opt_keep_running); } + if (!conf->image_constraints().validate(conf->argv()[ARGV_IMAGE_LOCATION])) { + return -1; + } + char restore_data_str[32]; if (snprintf(restore_data_str, sizeof(restore_data_str), "%i", conf->restore_data()) > static_cast(sizeof(restore_data_str)) - 1) { diff --git a/src/java.base/share/native/libcrexec/crexec.hpp b/src/java.base/share/native/libcrexec/crexec.hpp new file mode 100644 index 00000000000..7859f73d3f7 --- /dev/null +++ b/src/java.base/share/native/libcrexec/crexec.hpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025, Azul Systems, Inc. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +#ifndef CREXEC_HPP +#define CREXEC_HPP + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) + +#define CREXEC "crexec: " + +#ifndef PATH_MAX // For Windows +# define PATH_MAX 1024 +#endif + +#endif // CREXEC_HPP diff --git a/src/java.base/share/native/libcrexec/hashtable.hpp b/src/java.base/share/native/libcrexec/hashtable.hpp index b704e3f9cbb..bbf92091c9e 100644 --- a/src/java.base/share/native/libcrexec/hashtable.hpp +++ b/src/java.base/share/native/libcrexec/hashtable.hpp @@ -29,6 +29,7 @@ #include #include #include +#include template class Hashtable { @@ -43,7 +44,7 @@ class Hashtable { bool contains(const char *key) const; T *get(const char *key) const; - bool put(const char *key, T value); + template bool put(const char *key, TT&& value); private: size_t _length; @@ -113,6 +114,9 @@ bool Hashtable::contains(const char *key) const { template T *Hashtable::get(const char *key) const { + if (_length == 0) { + return nullptr; + } assert(key != nullptr); const unsigned int hash = string_hash(key) % _length; for (size_t i = hash; i < _length; i++) { @@ -128,13 +132,13 @@ T *Hashtable::get(const char *key) const { return nullptr; } -template -bool Hashtable::put(const char *key, T value) { +template template +bool Hashtable::put(const char* key, TT&& value) { T * const value_ptr = get(key); if (value_ptr == nullptr) { return false; } - *value_ptr = value; + *value_ptr = std::forward(value); return true; } diff --git a/src/java.base/share/native/libcrexec/image_constraints.cpp b/src/java.base/share/native/libcrexec/image_constraints.cpp new file mode 100644 index 00000000000..5e550a4f755 --- /dev/null +++ b/src/java.base/share/native/libcrexec/image_constraints.cpp @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2025, Azul Systems, Inc. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +#include +#include +#include +#include +#include + +#include "crexec.hpp" +#include "image_constraints.hpp" +#include "hashtable.hpp" + +#define LABEL_PREFIX "label:" +#define BITMAP_PREFIX "bitmap:" + +static FILE *open_tags(const char* image_location, const char* mode) { + char fname[PATH_MAX]; + if (snprintf(fname, sizeof(fname), "%s/tags", image_location) >= (int) sizeof(fname) - 1) { + fprintf(stderr, CREXEC "filename too long: %s/tags\n", image_location); + return nullptr; + } + FILE *f = fopen(fname, mode); + if (f == nullptr) { + fprintf(stderr, CREXEC "cannot open %s in mode %s: %s\n", fname, mode, strerror(errno)); + return nullptr; + } + return f; +} + +bool ImageConstraints::check_tag(const char* type, const char* name, size_t value_size) { + bool present = false; + _tags.foreach([&](Tag &tag) { + if (!strcmp(tag.name, name)) { + present = true; + } + }); + if (present) { + fprintf(stderr, CREXEC "%s %s is already set\n", type, name); + return false; + } else if (strpbrk(name, "=\n")) { + fprintf(stderr, CREXEC "%s name must not contain '=' or newline\n", type); + return false; + } + if (strlen(name) >= _MAX_NAME_SIZE) { + fprintf(stderr, CREXEC "%s %s is too long\n", type, name); + return false; + } + if (value_size >= _MAX_VALUE_SIZE) { + fprintf(stderr, CREXEC "%s %s value is too long (%zu bytes)\n", type, name, value_size); + return false; + } + return true; +} + +bool ImageConstraints::set_label(const char* name, const char* value) { + size_t value_size = strlen(value) + 1; + if (!check_tag("Label", name, value_size)) { + return false; + } else if (strchr(value, '\n')) { + fprintf(stderr, CREXEC "Label value must not contain a newline\n"); + return false; + } + char* name_copy = strdup(name); + char* value_copy = strdup(value); + if (name_copy == nullptr || value_copy == nullptr || !_tags.add( + Tag(TagType::LABEL, name_copy, value_copy, value_size))) { + fprintf(stderr, CREXEC "out of memory\n"); + free(name_copy); + free(value_copy); + return false; + } + return true; +} + +bool ImageConstraints::set_bitmap(const char* name, const unsigned char* value, size_t value_size) { + if (!check_tag("Bitmap", name, value_size)) { + return false; + } + char* name_copy = strdup(name); + void* bitmap_copy = malloc(value_size); + if (bitmap_copy != nullptr) { + memcpy(bitmap_copy, value, value_size); + } + if (name_copy == nullptr || bitmap_copy == nullptr || !_tags.add( + Tag(TagType::BITMAP, name_copy, (const unsigned char*) bitmap_copy, value_size))) { + fprintf(stderr, CREXEC "out of memory\n"); + free(name_copy); + free(bitmap_copy); + return false; + } + return true; +} + +bool ImageConstraints::persist(const char* image_location) const { + FILE* f = open_tags(image_location, "w"); + if (f == nullptr) { + return false; + } + _tags.foreach([&](const Tag &tag){ + if (tag.type == TagType::LABEL) { + fprintf(f, LABEL_PREFIX "%s=%s\n", tag.name, static_cast(tag.data)); + } else { + fprintf(f, BITMAP_PREFIX "%s=", tag.name); + const unsigned char* bytes = static_cast(tag.data); + for (const unsigned char* end = bytes + tag.data_size; bytes < end; bytes++) { + fprintf(f, "%02x",* bytes); + } + fputc('\n', f); + } + }); + if (fclose(f)) { + fprintf(stderr, CREXEC "cannot close %s/tags: %s\n", image_location, strerror(errno)); + return false; + } + return true; +} + +static inline unsigned char from_hex(char c, bool& err) { + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } else { + err = true; + return 0; + } +} + +static inline bool check_zeroes(const unsigned char* mem, size_t length) { + for (const unsigned char* end = mem + length; mem < end; ++mem) { + if (*mem) { + return false; + } + } + return true; +} + +bool ImageConstraints::Constraint::compare_bitmaps(const unsigned char* bitmap, size_t size) const { + size_t common_size = data_size < size ? data_size : size; + if (comparison == EQUALS) { + if (memcmp(data, bitmap, common_size)) { + return false; + } + if (data_size > size && !check_zeroes(static_cast(data) + common_size, data_size - common_size)) { + return false; + } else if (!check_zeroes(bitmap + common_size, size - common_size)) { + return false; + } + return true; + } + const unsigned char* bm1 = bitmap, * bm2 = static_cast(data); + size_t s1 = size, s2 = data_size; + if (comparison == SUPERSET) { + bm1 = bm2; + bm2 = bitmap; + s1 = s2; + s2 = size; + } + // Now test bm1 is subset of bm2 + for (size_t i = 0; i < common_size; ++i) { + if ((bm1[i] & bm2[i]) != bm1[i]) { + return false; + } + } + if (s1 > s2) { + return check_zeroes(bm1 + common_size, s1 - common_size); + } + return true; +} + +static void print_bitmap(const char* name, const unsigned char* data, size_t size) { + fprintf(stderr, CREXEC "\t%s", name); + for (size_t i = 0; i < size; ++i) { + fprintf(stderr, "%02x ", data[i]); + } + fputc('\n', stderr); +} + +bool ImageConstraints::validate(const char* image_location) const { + FILE* f = open_tags(image_location, "r"); + if (f == nullptr) { + return false; + } + char line[sizeof(BITMAP_PREFIX) + _MAX_NAME_SIZE + 1 + _MAX_VALUE_SIZE + 2]; + LinkedList tags; + while (fgets(line, (int) sizeof(line), f)) { + char* eq = strchr((char *) line, '='); + char* nl = strchr((char *) (eq + 1), '\n'); + if (eq == nullptr || nl == nullptr) { + fprintf(stderr, CREXEC "Invalid format of tags file: %s\n", line); + return false; + } + *eq = 0; + *nl = 0; + assert(eq < nl); + if (!strncmp(line, LABEL_PREFIX, strlen(LABEL_PREFIX))) { + char* name = strdup(line + strlen(LABEL_PREFIX)); + char* value = strdup(eq + 1); + if (name == nullptr || value == nullptr || !tags.add({ TagType::LABEL, name, value, (size_t) (nl - eq) })) { + fprintf(stderr, CREXEC "Cannot allocate memory for validation\n"); + free(name); + free(value); + return false; + } + } else if (!strncmp(line, BITMAP_PREFIX, strlen(BITMAP_PREFIX))) { + size_t length = (size_t)(nl - eq - 1)/2; + if (2 * length != (size_t)(nl - eq - 1)) { + fprintf(stderr, CREXEC "Invalid format of tags file (bad bitmap): %s\n", line); + return false; + } + unsigned char* data = (unsigned char*) malloc(length); + if (data == nullptr) { + fprintf(stderr, CREXEC "Cannot allocate memory for validation\n"); + return false; + } + bool err = false; + for (size_t i = 0; i < length; ++i) { + data[i] = (from_hex(eq[1 + 2 * i], err) << 4) + from_hex(eq[2 + 2 * i], err); + } + if (err) { + fprintf(stderr, CREXEC "Invalid format of tags file (bad character in bitmap): %s\n", line); + return false; + } + char* name = strdup(line + strlen(BITMAP_PREFIX)); + if (name == nullptr || !tags.add({ TagType::BITMAP, name, data, length })) { + fprintf(stderr, CREXEC "Cannot allocate memory for validation\n"); + free(name); + free(data); + return false; + } + } else { + fprintf(stderr, CREXEC "Invalid format of tags file (unknown type): %s\n", line); + return false; + } + } + const char** keys = new(std::nothrow) const char*[tags.size()]; + if (keys == nullptr) { + fprintf(stderr, CREXEC "Insufficient memory\n"); + return false; + } + int counter = 0; + tags.foreach([&](const Tag &t) { + keys[counter++] = t.name; + }); + Hashtable ht(keys, tags.size()); + delete[] keys; + + bool result = true; + tags.foreach([&](Tag &t) { + result = ht.put(t.name, std::move(t)) && result; + }); + _constraints.foreach([&](Constraint &c) { + Tag* t = ht.get(c.name); + c.failed = true; + if (t == nullptr) { + fprintf(stderr, CREXEC "Tag %s was not found\n", c.name); + } else if (t->type != c.type) { + fprintf(stderr, CREXEC "Type mismatch for tag %s\n", c.name); + } else if (c.type == TagType::LABEL && strcmp(static_cast(c.data), static_cast(t->data))) { + fprintf(stderr, CREXEC "Label mismatch for tag %s: '%s' vs. '%s'\n", c.name, + static_cast(c.data), static_cast(t->data)); + } else if (c.type == TagType::BITMAP && !c.compare_bitmaps(static_cast(t->data), t->data_size)) { + fprintf(stderr, CREXEC "Bitmap mismatch for tag %s:\n", c.name); + print_bitmap("Constraint: ", static_cast(c.data), c.data_size); + print_bitmap("Image: ", static_cast(t->data), t->data_size); + } else { + c.failed = false; + } + result = result && !c.failed; + }); + return result; +} diff --git a/src/java.base/share/native/libcrexec/image_constraints.hpp b/src/java.base/share/native/libcrexec/image_constraints.hpp new file mode 100644 index 00000000000..b715b5440cb --- /dev/null +++ b/src/java.base/share/native/libcrexec/image_constraints.hpp @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2025, Azul Systems, Inc. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +#ifndef IMAGE_CONSTRAINTS_HPP +#define IMAGE_CONSTRAINTS_HPP + +#include +#include +#include + +#include "crlib/crlib_image_constraints.h" +#include "linkedlist.hpp" + +class ImageConstraints { +private: + enum class TagType: std::uint8_t { + LABEL, + BITMAP, + }; + + struct Tag { + TagType type; + const char* name; + const void* data; + size_t data_size; + + Tag() = default; + Tag(TagType t, const char* n, const void* d, size_t ds): + type(t), name(n), data(d), data_size(ds) {} + + Tag(Tag &&o) { + type = o.type; + name = o.name; + data = o.data; + data_size = o.data_size; + o.name = nullptr; + o.data = nullptr; + } + + ~Tag() { + free((void*) name); + free((void*) data); + } + + Tag& operator=(Tag &&o) { + type = o.type; + name = o.name; + data = o.data; + data_size = o.data_size; + o.name = nullptr; + o.data = nullptr; + return *this; + } + }; + + struct Constraint { + TagType type; + bool failed; + const char* name; + const void* data; + size_t data_size; + crlib_bitmap_comparison_t comparison; + + Constraint(TagType t, const char* n, const void* d, size_t ds, crlib_bitmap_comparison_t c): + type(t), failed(false), name(n), data(d), data_size(ds), comparison(c) {} + + Constraint(Constraint &&o) { + type = o.type; + name = o.name; + data = o.data; + data_size = o.data_size; + comparison = o.comparison; + o.name = nullptr; + o.data = nullptr; + } + + ~Constraint() { + free((void*) name); + free((void*) data); + } + + + bool compare_bitmaps(const unsigned char* bitmap, size_t length) const; + }; + + LinkedList _tags; + LinkedList _constraints; + + static constexpr const size_t _MAX_NAME_SIZE = 256; + static constexpr const size_t _MAX_VALUE_SIZE = 256; + + bool check_tag(const char* type, const char* name, size_t value_size); + +public: + bool set_label(const char* name, const char* value); + bool set_bitmap(const char* name, const unsigned char* value, size_t length_bytes); + + bool require_label(const char* name, const char* value) { + return _constraints.add(Constraint(TagType::LABEL, strdup(name), strdup(value), strlen(value) + 1, EQUALS)); + } + + bool require_bitmap(const char* name, const unsigned char* value, size_t length_bytes, crlib_bitmap_comparison_t comparison) { + void* copy = malloc(length_bytes); + memcpy(copy, value, length_bytes); + return _constraints.add(Constraint(TagType::BITMAP, strdup(name), copy, length_bytes, comparison)); + } + + bool is_failed(const char* name) { + bool result = false; + _constraints.foreach([&](Constraint &c) { + if (!strcmp(c.name, name) && c.failed) { + result = true; + } + }); + return result; + } + + bool persist(const char* image_location) const; + bool validate(const char* image_location) const; +}; + +#endif // IMAGE_CONSTRAINTS_HPP diff --git a/src/java.base/share/native/libcrexec/linkedlist.hpp b/src/java.base/share/native/libcrexec/linkedlist.hpp new file mode 100644 index 00000000000..6ee2cc09f59 --- /dev/null +++ b/src/java.base/share/native/libcrexec/linkedlist.hpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2025, Azul Systems, Inc. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +#ifndef LINKEDLIST_HPP +#define LINKEDLIST_HPP + +#include +#include + +template class LinkedList { +private: + struct Node { + T _item; + Node* _next; + + template + Node(TT&& v): _item(std::forward(v)) {} + }; + + Node* _head = nullptr; + Node* _tail = nullptr; + size_t _size = 0; + + inline bool add_node(Node *node) { + if (node == nullptr) { + return false; + } + node->_next = nullptr; + if (_tail) { + _tail->_next = node; + _tail = node; + } else { + _tail = node; + } + if (!_head) { + _head = node; + } + ++_size; + return true; + } + +public: + ~LinkedList() { + Node *node = _head; + while (node) { + Node *next = node->_next; + delete node; + node = next; + } + } + + bool add(T&& item) { + return add_node(new(std::nothrow) Node(std::move(item))); + } + + bool add(const T& item) { + return add_node(new(std::nothrow) Node(item)); + } + + template void foreach(Func f) const { + Node* node = _head; + while (node) { + f(node->_item); + node = node->_next; + } + } + + size_t size() const { + return _size; + } +}; + +#endif // LINKEDLIST_HPP diff --git a/test/jdk/jdk/crac/CPUFeatures/CPUFeatures.sh b/test/jdk/jdk/crac/CPUFeatures/CPUFeatures.sh index bfabd27bd59..d81716f73ea 100755 --- a/test/jdk/jdk/crac/CPUFeatures/CPUFeatures.sh +++ b/test/jdk/jdk/crac/CPUFeatures/CPUFeatures.sh @@ -1,4 +1,4 @@ -#! /bin/bash +#! /bin/sh # Copyright (c) 2025, Azul Systems, Inc. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # @@ -24,13 +24,15 @@ # @compile CPUFeatures.java # @comment It will be always skipped unless you use jtreg option "-manual" which conflicts with JDK's default option "-automatic". # @comment Therefore either change it in make/RunTests.gmk or run jtreg by hand. +# @comment Also consider using -J-Djavatest.maxOutputSize=999999999 to capture the whole log. # @run shell/manual CPUFeatures.sh -set -ex -o pipefail +set -ex +# JTReg does not respect shebang, and 'pipefail' is not available in dash: set -o pipefail exec >&2 JAVA_HOME=$TESTJAVA -javafiles="{bin/{java,jcmd},lib/{jvm.cfg,lib{crexec,java,jimage,jli,jsvml,net,nio,attach,zip}.so,modules,tzdb.dat,server/{classes.jsa,libjvm.so},criuengine,criu},conf/security/java.security}" +javafiles="bin/java bin/jcmd lib/jvm.cfg lib/libcrexec.so lib/libjava.so lib/libjimage.so lib/libjli.so lib/libjsvml.so lib/libnet.so lib/libnio.so lib/libattach.so lib/libzip.so lib/modules lib/tzdb.dat lib/server/classes.jsa lib/server/libjvm.so lib/criuengine lib/criu conf/security/java.security" qemuimgurl=https://download.fedoraproject.org/pub/fedora/linux/releases/41/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-41-1.4.x86_64.qcow2 qemuimgsumurl=https://download.fedoraproject.org/pub/fedora/linux/releases/41/Cloud/x86_64/images/Fedora-Cloud-41-1.4-x86_64-CHECKSUM # FIXME: criu need an update for new kernels: @@ -48,7 +50,8 @@ if ! grep "$qemuimgsumgrep" $qemuimgsumfile;then exit 1 fi fi -function checksum + +checksum() { (cd $qemuimgdir grep "^[^#].*$(basename $qemuimgfile)" $qemuimgsumfile|sha256sum -c -|grep "^$(basename $qemuimgurl): OK$" @@ -82,6 +85,7 @@ LIBGUESTFS_BACKEND=direct guestmount -a $qemuimg -i $mountdir sed -i -e 's/^options /&selinux=0 /' $mountdir/boot/loader/entries/*.conf sed -i -e 's/^root:x:/root::/' $mountdir/etc/passwd cat $sshkey.pub >>$mountdir/root/.ssh/authorized_keys +chmod 600 $mountdir/root/.ssh/authorized_keys rm -f $mountdir/usr/lib/systemd/zram-generator.conf echo kernel.core_pattern=core >>$mountdir/etc/sysctl.d/CPUFeatures.conf guestunmount $mountdir @@ -89,7 +93,9 @@ rmdir $mountdir # -nographic may not be suitable for every OS/image for try in $(seq 1 10);do - sshport=$[$RANDOM+1024] + # Dash does not have $RANDOM + MY_RANDOM=$(awk 'BEGIN { srand(); printf "%d\n", 32768 * rand() }') + sshport=$(($MY_RANDOM+1024)) sshporthex=$(printf %04X $sshport) if ! grep -q "^..............:$sshporthex 00000000:0000 0A " /proc/net/tcp \ && ! grep -q "^....: 00000000000000000000000000000000:$sshporthex 00000000000000000000000000000000:0000 0A " /proc/net/tcp6 \ @@ -103,26 +109,28 @@ test -n "$sshport" qemuimg2=$tmpdir/CPUFeatures-run.qcow2 qemuargs="-m 4096 -net nic -net user,hostfwd=tcp::$sshport-:22 -drive format=qcow2,media=disk,cache=unsafe,file=$qemuimg2 -nographic" -function runssh { - ssh -i $sshkey -p $sshport -o "UserKnownHostsFile /dev/null" -o "StrictHostKeyChecking no" -o "ConnectTimeout $[10*$timeoutmultiply]" root@127.0.0.1 "$@" +runssh() { + ssh -i $sshkey -p $sshport -o "UserKnownHostsFile /dev/null" -o "StrictHostKeyChecking no" -o "ConnectTimeout $((10*$timeoutmultiply))" root@127.0.0.1 "$@" } # Do not use /tmp as that may not survive a reboot on some Linux distributions. qemudir="/root/CPUFeatures" # in reality it is about 20 qemustarttimeout=120 -for file in $(eval echo $JAVA_HOME/$javafiles);do - test -e $file +for file in $javafiles; do + test -e $JAVA_HOME/$file done rm -f $qemuimg2 missingfiles=true -function qemucopyfiles { + +qemucopyfiles() { missingfiles=false (cd $JAVA_HOME;tar chf - $(eval echo $javafiles))|runssh "set -ex;mkdir $qemudir;cd $qemudir;tar xf -;JAVA_HOME=\$PWD bin/java -XX:+ShowCPUFeatures --version" (cd $TESTCLASSES;tar cf - CPUFeatures.class)|runssh "set -ex;cd $qemudir;tar xf -" } qemustarted="" -function qemustart { + +qemustart() { type=$1 cpu=$2 accel="" @@ -149,7 +157,7 @@ function qemustart { qemustarted="$qemustarted_check" t0=$(date +%s) while true;do - if [ $[$(date +%s)-$t0] -ge $[$qemustarttimeout*$timeoutmultiply] ];then + if [ $(($(date +%s)-$t0)) -ge $(($qemustarttimeout*$timeoutmultiply)) ];then echo >&2 "qemu timeout, qemu PID=$qemupid" return 1 fi @@ -161,15 +169,18 @@ function qemustart { qemucopyfiles fi } -function qemustop { + +qemustop() { if [ -z "$qemustarted" ];then return fi - runssh poweroff || kill $qemupid || : + runssh poweroff || : + # || kill $qemupid || : wait || : qemustarted="" } -function qemuimg2rebuild { + +qemuimg2rebuild() { qemustop qemu-img create -b $qemuimg -F qcow2 -f qcow2 $qemuimg2 test -e $qemuimg2 @@ -180,21 +191,24 @@ function qemuimg2rebuild { missingfiles=true } javasetup="cd $qemudir;export JAVA_HOME=\$PWD;ulimit -c unlimited" -function checkpoint { + +checkpoint() { checkpoint_args="$1" runssh "$javasetup;rm -rf cr || exit 1; \ $(: 'Prevent on CRIU: Error (criu/pie/restorer.c:2057): Unable to create a thread: -17') \ for i in \$(seq 1 1000);do /bin/true;done; \ - bin/java -XX:CRaCCheckpointTo=cr -XX:+ShowCPUFeatures $checkpoint_args CPUFeatures&p=\$!;sleep $[3*$timeoutmultiply];bin/jcmd CPUFeatures JDK.checkpoint; \ + bin/java -XX:CRaCCheckpointTo=cr -XX:+ShowCPUFeatures $checkpoint_args CPUFeatures&p=\$!;sleep $((3*$timeoutmultiply));bin/jcmd CPUFeatures JDK.checkpoint; \ wait \$p;true" } -function restore { + +restore() { restore_args="$1" - restore="$(runssh "$javasetup;bin/java -XX:CRaCRestoreFrom=cr $restore_args&p=\$!;(sleep $[6*$timeoutmultiply];kill \$p)&wait \$p;echo RC=\$?" 2>&1|tee /proc/self/fd/2)" + restore="$(runssh "$javasetup;bin/java -XX:CRaCRestoreFrom=cr $restore_args&p=\$!;(sleep $((6*$timeoutmultiply));kill \$p)&wait \$p;echo RC=\$?" 2>&1|tee /proc/self/fd/2)" } failfile=$tmpdir/failfile rm -f $failfile -function checkpoint_restore { + +checkpoint_restore() { kind_checkpoint="$1" kind_restore="$2" check="${3:-CPUFeaturesCheck }" @@ -211,7 +225,8 @@ function checkpoint_restore { } # SIGTERM+128; see 'kill \$p' above expectRC=143 -function checkpoint_restore_result { + +checkpoint_restore_result() { rc=$? if ! echo $restore|grep RC=$expectRC;then rc=99 @@ -222,28 +237,35 @@ function checkpoint_restore_result { echo -n "PASS"; else echo -n "FAIL" - touch $failfile + echo "$restore" > $failfile + exit 255 # fail fast fi echo ": criu: " set -x } -function get_features { + +get_features() { runssh "set -ex;cd $qemudir;JAVA_HOME=\$PWD bin/java -XX:+ShowCPUFeatures --version"|sed -n 's/^This machine.s CPU features are: -XX:CPUFeatures=//p' } exitcode=0 shutdown_done=false -function shutdown { + +shutdown() { if $shutdown_done;then return;fi shutdown_done=true qemustop - rm -f $qemuimg2 # CPUFeatures.class - rm -f $qemuimg $sshkey $sshkey.pub - if [ -e $failfile ];then exitcode=1;fi - rm -f $failfile - rmdir $tmpdir + #rm -f $qemuimg2 # CPUFeatures.class + #rm -f $qemuimg $sshkey $sshkey.pub + if [ -e $failfile ]; then + ls -l $failfile || true + exitcode=1; + fi + #rm -f $failfile + #rmdir $tmpdir } trap shutdown EXIT -function fatal { + +fatal() { shutdown echo >&2 "$*" exit 1 @@ -254,16 +276,16 @@ if [ -z "$*" ];then # Verify reproducibility of: https://jira.azulsystems.com/browse/ZULU-53749 qemuimg2rebuild qemustart kvm host -if ! get_features|perl -lne ' - $a=0x4ff7fff9dfcfbf7; - $b=0x1e6; - /^(.*),(.*)$/ or die; - die sprintf "FA"."IL: 0x%x required vs. 0x%x found. 0x%x required vs. 0x%x found.\n",$a,eval $1,$b,eval $2 if $a&~eval $1||$b&~eval $2; - print "PA"."SS: Initial CPU check" -';then - # One could verify whether lower CPU isn't sufficient. E5-2630v3 is too old, it does not reproduce ZULU-53749. - fatal "FA$(: )IL: CPU i7-1165G7 or higher required" -fi +# if ! get_features|perl -lne ' +# $a=0x4ff7fff9dfcfbf7; +# $b=0x1e6; +# /^(.*),(.*)$/ or die; +# die sprintf "FA"."IL: 0x%x required vs. 0x%x found. 0x%x required vs. 0x%x found.\n",$a,eval $1,$b,eval $2 if $a&~eval $1||$b&~eval $2; +# print "PA"."SS: Initial CPU check" +# ';then +# # One could verify whether lower CPU isn't sufficient. E5-2630v3 is too old, it does not reproduce ZULU-53749. +# fatal "FA$(: )IL: CPU i7-1165G7 or higher required" +# fi # Opteron_G1 is the most basic CPU (x86_64) # SandyBridge is the first CPU with OSXSAVE+XSAVE (0x0,0x24) @@ -283,31 +305,33 @@ checkpoint_restore "kvm host" "kvm host" # IvyBridge is the first superset of SandyBridge expectRC_save=$expectRC expectRC=1 +errorMsg="Bitmap mismatch for tag cpu.features" checkpoint_restore "kvm IvyBridge" "kvm SandyBridge" - -(set +e; echo "$restore"|grep "You have to specify" && ! echo "$restore"|grep "CPUFeaturesCheck ";checkpoint_restore_result) +(set +e; echo "$restore"|grep "$errorMsg" && ! echo "$restore"|grep "CPUFeaturesCheck ";checkpoint_restore_result) expectRC=$expectRC_save -checkpoint_restore "kvm IvyBridge" "kvm SandyBridge" - "-XX:CPUFeatures=0x142100054bbd7,0xe4" -(set +e;! echo "$restore"|grep "You have to specify" && echo "$restore"|grep "CPUFeaturesCheck ";checkpoint_restore_result) +# Don't set movbe and shstk in CPU features... +checkpoint_restore "kvm IvyBridge" "kvm SandyBridge" - "-XX:CPUFeatures=0x142100054bbd7,0xc0" +(set +e;! echo "$restore"|grep "$errorMsg" && echo "$restore"|grep "CPUFeaturesCheck ";checkpoint_restore_result) -# This does not crash the guest despite it could. +# This does not crash (or print errors) despite it could. checkpoint_restore "kvm IvyBridge" "kvm SandyBridge" - "" "-XX:+UnlockExperimentalVMOptions -XX:+IgnoreCPUFeatures" -(set +e; echo "$restore"|grep "You have to specify" && echo "$restore"|grep "CPUFeaturesCheck ";checkpoint_restore_result) +(set +e;! echo "$restore"|grep "$errorMsg" && echo "$restore"|grep "CPUFeaturesCheck ";checkpoint_restore_result) # IgnoreCPUFeatures is not inherited from snapshot to restore. expectRC_save=$expectRC expectRC=1 checkpoint_restore "kvm IvyBridge" "kvm SandyBridge" - "-XX:+UnlockExperimentalVMOptions -XX:+IgnoreCPUFeatures" "" -(set +e; echo "$restore"|grep "You have to specify" && ! echo "$restore"|grep "CPUFeaturesCheck ";checkpoint_restore_result) +(set +e; echo "$restore"|grep "$errorMsg" && ! echo "$restore"|grep "CPUFeaturesCheck ";checkpoint_restore_result) expectRC=$expectRC_save checkpoint_restore "kvm IvyBridge" "kvm SandyBridge" - "-XX:+UnlockExperimentalVMOptions -XX:-IgnoreCPUFeatures" \ "-XX:+UnlockExperimentalVMOptions -XX:+IgnoreCPUFeatures" -(set +e; echo "$restore"|grep "You have to specify" && echo "$restore"|grep "CPUFeaturesCheck ";checkpoint_restore_result) +(set +e;! echo "$restore"|grep "$errorMsg" && echo "$restore"|grep "CPUFeaturesCheck ";checkpoint_restore_result) checkpoint_restore "kvm IvyBridge" "kvm SandyBridge" - "-XX:+UnlockExperimentalVMOptions -XX:+IgnoreCPUFeatures" \ "-XX:+UnlockExperimentalVMOptions -XX:-IgnoreCPUFeatures" expectRC=1 -(set +e; echo "$restore"|grep "You have to specify" && ! echo "$restore"|grep "CPUFeaturesCheck ";checkpoint_restore_result) +(set +e; echo "$restore"|grep "$errorMsg" && ! echo "$restore"|grep "CPUFeaturesCheck ";checkpoint_restore_result) expectRC=$expectRC_save # FAIL: https://jira.azulsystems.com/browse/ZULU-53749 @@ -317,16 +341,28 @@ checkpoint_restore "kvm host" "kvm SandyBridge" - # v #checkpoint_restore "system-x86_64 max" "kvm SandyBridge" - # it does not work # The crash was RC=139 expectRC=1 -(set +e;echo "$restore"|grep "You have to specify" && ! echo "$restore"|grep "CPUFeaturesCheck ";checkpoint_restore_result) +(set +e;echo "$restore"|grep "$errorMsg" && ! echo "$restore"|grep "CPUFeaturesCheck ";checkpoint_restore_result) expectRC=$expectRC_save +# What's the point of host -> VM tests when this can be run on arbitrary host? Works for me... +if false; then checkpoint_restore "kvm host" "kvm SandyBridge" - "" "-XX:+UnlockExperimentalVMOptions -XX:+IgnoreCPUFeatures" expectRC=139 -(set +e;echo "$restore"|grep "You have to specify" && ! echo "$restore"|grep "CPUFeaturesCheck ";checkpoint_restore_result) +(set +e;echo "$restore"|grep "$errorMsg" && ! echo "$restore"|grep "CPUFeaturesCheck ";checkpoint_restore_result) expectRC=$expectRC_save +fi checkpoint_restore "kvm IvyBridge" "kvm IvyBridge" "" "-XX:CPUFeatures=native" -checkpoint_restore "kvm IvyBridge" "kvm IvyBridge" "" "-XX:CPUFeatures=ignore" + +# Setting -XX:CPUFeatures=ignore on checkpoint means that we're not writing the features at all, +# and subsequent restore requires "-XX:+UnlockExperimentalVMOptions -XX:+IgnoreCPUFeatures" +expectRC=1 +checkpoint_restore "kvm IvyBridge" "kvm IvyBridge" - "-XX:CPUFeatures=ignore" +(set +e;echo "$restore"|grep "Restore failed due to incompatible or missing CPU features" && ! echo "$restore"|grep "CPUFeaturesCheck ";checkpoint_restore_result) +expectRC=$expectRC_save + +checkpoint_restore "kvm IvyBridge" "kvm IvyBridge" "" "-XX:CPUFeatures=ignore" "-XX:+UnlockExperimentalVMOptions -XX:+IgnoreCPUFeatures" + checkpoint_restore "kvm IvyBridge" "kvm SandyBridge" "" "-XX:CPUFeatures=generic" if false;then # too slow, failing diff --git a/test/jdk/jdk/crac/ignoreRestore/NoCPUFeaturesTest.java b/test/jdk/jdk/crac/ignoreRestore/NoCPUFeaturesTest.java index a9ef97ae150..ecb07742820 100644 --- a/test/jdk/jdk/crac/ignoreRestore/NoCPUFeaturesTest.java +++ b/test/jdk/jdk/crac/ignoreRestore/NoCPUFeaturesTest.java @@ -49,7 +49,7 @@ public static void main(String[] args) throws Exception { builder.startRestoreWithArgs(null, List.of(Main.class.getName(), "false")) .waitForSuccess().outputAnalyzer() - .shouldContain("incompatible CPU features").shouldContain(MAIN_MSG); + .shouldContain("cannot open cr/tags in mode r").shouldContain(MAIN_MSG); } public static class Main {