diff --git a/.gitignore b/.gitignore index 1e92e88fb77fa..e925e70990ecd 100644 --- a/.gitignore +++ b/.gitignore @@ -98,6 +98,7 @@ config.h.in /main/php_config.h.in /main/php_config.h /Zend/zend_config.h +/Zend/zend_abi_signature.h # ------------------------------------------------------------------------------ # Manual (man 1 and 8) pages generated from templates for *nix alike systems @@ -125,6 +126,7 @@ config.h.in # ------------------------------------------------------------------------------ # Executable binaries and scripts generated during the build process # ------------------------------------------------------------------------------ +/Zend/gen_abi_sig /ext/phar/phar.phar /ext/phar/phar.php /pear/install-pear-nozlib.phar diff --git a/Zend/Makefile.frag b/Zend/Makefile.frag index 72bb485dc04ab..6764976b893d8 100644 --- a/Zend/Makefile.frag +++ b/Zend/Makefile.frag @@ -42,3 +42,28 @@ vm.gen.intermediate: $(srcdir)/zend_vm_def.h $(srcdir)/zend_vm_execute.skl $(src $(builddir)/zend_highlight.lo $(builddir)/zend_compile.lo: $(srcdir)/zend_language_parser.h Zend/zend_execute.lo: $(srcdir)/zend_vm_execute.h $(srcdir)/zend_vm_opcodes.h + +# Define the headers that constitute the ABI +ABI_HEADERS = \ + $(top_srcdir)/Zend/zend_types.h \ + $(top_srcdir)/Zend/zend_API.h \ + $(top_srcdir)/Zend/zend_compile.h \ + $(top_srcdir)/ext/opcache/zend_persist.h \ + $(top_srcdir)/Zend/zend_vm_opcodes.h + +# Rule to build our generator program. +# This is a simple, self-contained C file with no special dependencies. +Zend/gen_abi_sig: Zend/gen_abi_sig.c + $(CC) $(CFLAGS_CLEAN) $(EXTRA_CFLAGS) $< -o $@ + +# Rule to generate the ABI signature string and embed it into a header file +Zend/zend_abi_signature.h: Zend/gen_abi_sig $(ABI_HEADERS) + @echo "Generating Opcache ABI Signature..." + @echo "/* This file is automatically generated. DO NOT EDIT. */" > $@ + @echo "#ifndef ZEND_ABI_SIGNATURE_H" >> $@ + @echo "#define ZEND_ABI_SIGNATURE_H" >> $@ + @echo "static const char *zend_opcache_abi_signature = \"" `$(CC) -E $(CPPFLAGS) $(INCLUDES) $(ABI_HEADERS) | $(top_builddir)/Zend/gen_abi_sig` "\";" >> $@ + @echo "#endif" >> $@ + +# Ensure the system_id object depends on the generated header +Zend/zend_system_id.lo: Zend/zend_abi_signature.h diff --git a/Zend/gen_abi_sig.c b/Zend/gen_abi_sig.c new file mode 100644 index 0000000000000..e35f3ea819251 --- /dev/null +++ b/Zend/gen_abi_sig.c @@ -0,0 +1,74 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Samuel Melrose | + +----------------------------------------------------------------------+ +*/ + +/* + * Opcache ABI Signature Generator + * + * This program is compiled and run during the PHP build process. + * It reads pre-processed C header source from stdin, calculates a + * CRC32 checksum of it, and prints the hex digest to stdout. + * + * A simple CRC32 is used as a lightweight, dependency-free way to + * detect changes in the core data structures. + */ + + #include + #include + + /* A self-contained CRC32 implementation */ + static uint32_t crc32_table[256]; + + static void build_crc32_table(void) { + for (uint32_t i = 0; i < 256; i++) { + uint32_t ch = i; + uint32_t crc = 0; + for (size_t j = 0; j < 8; j++) { + uint32_t b = (ch ^ crc) & 1; + crc >>= 1; + if (b) { + crc = crc ^ 0xEDB88320; + } + ch >>= 1; + } + crc32_table[i] = crc; + } + } + + static uint32_t crc32_update(uint32_t crc, const unsigned char *buf, size_t len) { + crc = ~crc; + while (len--) { + crc = (crc >> 8) ^ crc32_table[(crc ^ *buf++) & 0xFF]; + } + return ~crc; + } + + int main(int argc, char *argv[]) { + uint32_t crc = 0; + unsigned char buf[4096]; + size_t n; + + build_crc32_table(); + + while ((n = fread(buf, 1, sizeof(buf), stdin)) > 0) { + crc = crc32_update(crc, buf, n); + } + + printf("%08x", crc); + + return 0; + } diff --git a/Zend/zend.c b/Zend/zend.c index 2d8a0f455f8b4..7eeea1fefb595 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -278,6 +278,7 @@ ZEND_INI_BEGIN() /* Subtracted from the max allowed stack size, as a buffer, when checking for overflow. 0: auto detect. */ STD_ZEND_INI_ENTRY("zend.reserved_stack_size", "0", ZEND_INI_SYSTEM, OnUpdateReservedStackSize, reserved_stack_size, zend_executor_globals, executor_globals) #endif + STD_ZEND_INI_BOOLEAN("zend.portable_build", "0", ZEND_INI_SYSTEM, OnUpdateBool, portable_build, zend_executor_globals, executor_globals) ZEND_INI_END() diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index b4e94ec1f9892..7efad9ee756d4 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -249,6 +249,9 @@ struct _zend_executor_globals { /* timeout support */ zend_long timeout_seconds; + /* portable system_id */ + bool portable_build; + HashTable *ini_directives; HashTable *modified_ini_directives; zend_ini_entry *error_reporting_ini_entry; diff --git a/Zend/zend_system_id.c b/Zend/zend_system_id.c index 2c3ebab0f4807..9d96b13c57f04 100644 --- a/Zend/zend_system_id.c +++ b/Zend/zend_system_id.c @@ -18,6 +18,7 @@ #include "php.h" #include "zend_system_id.h" #include "zend_extensions.h" +#include "zend_abi_signature.h" #include "ext/standard/md5.h" #include "ext/hash/php_hash.h" @@ -44,14 +45,6 @@ ZEND_API zend_result zend_add_system_entropy(const char *module_name, const char void zend_startup_system_id(void) { PHP_MD5Init(&context); - PHP_MD5Update(&context, PHP_VERSION, sizeof(PHP_VERSION)-1); - PHP_MD5Update(&context, ZEND_EXTENSION_BUILD_ID, sizeof(ZEND_EXTENSION_BUILD_ID)-1); - PHP_MD5Update(&context, ZEND_BIN_ID, sizeof(ZEND_BIN_ID)-1); - if (strstr(PHP_VERSION, "-dev") != 0) { - /* Development versions may be changed from build to build */ - PHP_MD5Update(&context, __DATE__, sizeof(__DATE__)-1); - PHP_MD5Update(&context, __TIME__, sizeof(__TIME__)-1); - } zend_system_id[0] = '\0'; } @@ -66,6 +59,27 @@ void zend_finalize_system_id(void) unsigned char digest[16]; uint8_t hooks = 0; + if (EG(portable_build)) { + /* Portable build mode: Use the ABI signature and major/minor version */ + int major_version = PHP_MAJOR_VERSION; + int minor_version = PHP_MINOR_VERSION; + + PHP_MD5Update(&context, (const unsigned char *)&major_version, sizeof(int)); + PHP_MD5Update(&context, (const unsigned char *)&minor_version, sizeof(int)); + PHP_MD5Update(&context, (const unsigned char *)zend_opcache_abi_signature, strlen(zend_opcache_abi_signature)); + } else { + /* Default strict mode: Use the original full-fat build ID */ + PHP_MD5Update(&context, PHP_VERSION, sizeof(PHP_VERSION)-1); + PHP_MD5Update(&context, ZEND_EXTENSION_BUILD_ID, sizeof(ZEND_EXTENSION_BUILD_ID)-1); + if (strstr(PHP_VERSION, "-dev") != 0) { + PHP_MD5Update(&context, __DATE__, sizeof(__DATE__)-1); + PHP_MD5Update(&context, __TIME__, sizeof(__TIME__)-1); + } + } + + /* These are always critical for compatibility */ + PHP_MD5Update(&context, ZEND_BIN_ID, sizeof(ZEND_BIN_ID)-1); + if (zend_ast_process) { hooks |= ZEND_HOOK_AST_PROCESS; } diff --git a/ext/opcache/tests/cleanup_helper.inc b/ext/opcache/tests/cleanup_helper.inc new file mode 100644 index 0000000000000..f61fb25c71b1b --- /dev/null +++ b/ext/opcache/tests/cleanup_helper.inc @@ -0,0 +1,19 @@ +isDir()) { + @rmdir($fileinfo->getRealPath()); + } else { + @unlink($fileinfo->getRealPath()); + } + } + @rmdir($dir); + } catch (UnexpectedValueException $e) { @rmdir($dir); } catch (Exception $e) { @rmdir($dir); } +} \ No newline at end of file diff --git a/ext/opcache/tests/portable_build.phpt b/ext/opcache/tests/portable_build.phpt new file mode 100644 index 0000000000000..f938e7e37bade --- /dev/null +++ b/ext/opcache/tests/portable_build.phpt @@ -0,0 +1,79 @@ +--TEST-- +Zend Opcache: Check zend.portable_build system_id for file cache +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=disable +opcache.file_cache="{PWD}/portable_build_cache" +--EXTENSIONS-- +opcache +--FILE-- + 'opcache', + 'opcache.enable' => 1, + 'opcache.enable_cli' => 1, + 'opcache.jit' => 'disable', + 'opcache.file_cache' => $cache_dir, + 'opcache.file_update_protection' => 0, + 'zend.portable_build' => 1, +]; + +$ini_str_portable = ''; +foreach ($ini_settings_portable as $key => $value) { + $ini_str_portable .= "-d " . escapeshellarg("$key=$value") . " "; +} + +$php_executable = getenv('TEST_PHP_EXECUTABLE'); +$command_portable = "$php_executable $ini_str_portable -r 'opcache_compile_file(\"$test_file\"); echo opcache_get_status()[\"system_id\"];'"; +$portable_system_id = trim(shell_exec($command_portable)); + +if (substr(PHP_OS, 0, 3) === 'WIN') { + $pattern = $cache_dir . DIRECTORY_SEPARATOR . $portable_system_id . DIRECTORY_SEPARATOR . '*' . DIRECTORY_SEPARATOR . str_replace(':', '', __FILE__) . '.bin'; +} else { + $pattern = $cache_dir . DIRECTORY_SEPARATOR . $portable_system_id . DIRECTORY_SEPARATOR . __FILE__ . '.bin'; +} +$cache_files = glob($pattern); +if (count($cache_files) !== 1) { + die('Failed to find the generated cache file in ' . $cache_dir); +} +$cache_file_path = $cache_files[0]; +$cache_file_handle = fopen($cache_file_path, 'rb'); +fread($cache_file_handle, 8); // skip the first 8 bytes +$header = fread($cache_file_handle, 32); // Read the first 32 bytes (the system_id) +fclose($cache_file_handle); +$stored_system_id = trim($header); + +var_dump($portable_system_id === $stored_system_id); + +$default_system_id = opcache_get_status()['system_id']; + +var_dump($portable_system_id === $default_system_id); + +?> +--CLEAN-- + +--EXPECT-- +bool(true) +bool(false) diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index a4f632872f546..3c22db56ee61e 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -23,6 +23,7 @@ #include "php.h" #include "ZendAccelerator.h" +#include "zend_system_id.h" #include "zend_API.h" #include "zend_closures.h" #include "zend_shared_alloc.h" @@ -651,6 +652,7 @@ ZEND_FUNCTION(opcache_get_status) if (ZCG(accel_directives).file_cache) { add_assoc_string(return_value, "file_cache", ZCG(accel_directives).file_cache); + add_assoc_stringl(return_value, "system_id", zend_system_id, 32); } if (file_cache_only) { add_assoc_bool(return_value, "file_cache_only", 1);