Skip to content

Opcache: Introduce ABI-based versioning for file cache portability #19123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions Zend/Makefile.frag
Original file line number Diff line number Diff line change
Expand Up @@ -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
74 changes: 74 additions & 0 deletions Zend/gen_abi_sig.c
Original file line number Diff line number Diff line change
@@ -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 |
| [email protected] so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Samuel Melrose <[email protected]> |
+----------------------------------------------------------------------+
*/

/*
* 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 <stdio.h>
#include <stdint.h>

/* 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;
}
1 change: 1 addition & 0 deletions Zend/zend.c
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
3 changes: 3 additions & 0 deletions Zend/zend_globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
30 changes: 22 additions & 8 deletions Zend/zend_system_id.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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';
}

Expand All @@ -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;
}
Expand Down
19 changes: 19 additions & 0 deletions ext/opcache/tests/cleanup_helper.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

function removeDirRecursive($dir) {
if (!is_dir($dir)) return;
try {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $fileinfo) {
if ($fileinfo->isDir()) {
@rmdir($fileinfo->getRealPath());
} else {
@unlink($fileinfo->getRealPath());
}
}
@rmdir($dir);
} catch (UnexpectedValueException $e) { @rmdir($dir); } catch (Exception $e) { @rmdir($dir); }
}
79 changes: 79 additions & 0 deletions ext/opcache/tests/portable_build.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
--TEST--
Zend Opcache: Check zend.portable_build system_id for file cache
--SKIPIF--
<?php
// Ensure the cache directory exists BEFORE OPcache needs it
$cacheDir = __DIR__ . '/portable_build_cache';
if (!is_dir($cacheDir)) {
@mkdir($cacheDir, 0777, true);
}
// Check if mkdir failed potentially due to permissions
if (!is_dir($cacheDir) || !is_writable($cacheDir)) {
die('skip Could not create or write to cache directory: ' . $cacheDir);
}
?>
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.jit=disable
opcache.file_cache="{PWD}/portable_build_cache"
--EXTENSIONS--
opcache
--FILE--
<?php
$cache_dir = __DIR__ . '/portable_build_cache';
$test_file = __FILE__;

$ini_settings_portable = [
'zend_extension' => '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--
<?php
require __DIR__ . '/cleanup_helper.inc';

$cacheDir = __DIR__ . '/portable_build_cache';

removeDirRecursive($cacheDir);
?>
--EXPECT--
bool(true)
bool(false)
2 changes: 2 additions & 0 deletions ext/opcache/zend_accelerator_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand Down
Loading