diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 97b82378780b8..52804217221ac 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2120,7 +2120,7 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) unsigned int checksum = zend_accel_script_checksum(persistent_script); if (checksum != persistent_script->dynamic_members.checksum ) { /* The checksum is wrong */ - zend_accel_error(ACCEL_LOG_INFO, "Checksum failed for '%s': expected=0x%08x, found=0x%08x", + zend_accel_error(ACCEL_LOG_WARNING, "Checksum failed for '%s': expected=0x%08x, found=0x%08x", ZSTR_VAL(persistent_script->script.filename), persistent_script->dynamic_members.checksum, checksum); zend_shared_alloc_lock(); if (!persistent_script->corrupted) { @@ -4763,6 +4763,12 @@ static int accel_finish_startup(void) ZCG(cwd_key_len) = 0; ZCG(cwd_check) = 1; + ZCG(mem_checksum_skip_list) = NULL; + ZCG(mem_checksum_skip_list_count) = 0; +#if ZEND_DEBUG + ZCG(mem_checksum_skip_list_capacity) = 0; +#endif + if (accel_preload(ZCG(accel_directives).preload, in_child) != SUCCESS) { ret = FAILURE; } diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h index de5ec45de72c8..aecc8b6ef93ef 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -109,6 +109,19 @@ typedef enum _zend_accel_restart_reason { ACCEL_RESTART_USER /* restart scheduled by opcache_reset() */ } zend_accel_restart_reason; +typedef struct _zend_accel_skip_list_entry { + /* The offset indicates which byte offset within the memory block must be skipped. + * The size of the skip is equal to the system's pointer size. */ + size_t offset; + /* To prevent creating a huge list with a lot of entries, we use a compression scheme + * based on the following two fields. If repetitions > 0, then the checksum algorithm + * will repeat `repetitions` times checksumming `checked_area_size` bytes, followed + * by skipping a pointer. For example, we use this to skip the `zend_op->handler` pointer. + * This allows us to only create one skip list entry per op array to handle *all* the opcodes. */ + uint32_t checked_area_size; + uint32_t repetitions; +} zend_accel_skip_list_entry; + typedef struct _zend_persistent_script { zend_script script; zend_long compiler_halt_offset; /* position of __HALT_COMPILER or -1 */ @@ -123,6 +136,10 @@ typedef struct _zend_persistent_script { void *mem; /* shared memory area used by script structures */ size_t size; /* size of used shared memory */ + /* Some bytes must be skipped in the checksum calculation because they could've changed (legally) + * since the last checksum calculation. */ + zend_accel_skip_list_entry *mem_checksum_skip_list; + /* All entries that shouldn't be counted in the ADLER32 * checksum must be declared in this struct */ @@ -225,6 +242,16 @@ typedef struct _zend_accel_globals { /* preallocated buffer for keys */ zend_string key; char _key[MAXPATHLEN * 8]; + + zend_accel_skip_list_entry *mem_checksum_skip_list; + uint32_t mem_checksum_skip_list_count; + /* We don't actually need the capacity because the skip list is preallocated to the right size. + * We only use this in a debug build to check for bugs. If an assertion failure would ever trigger + * for this field, then it's signifies a bug in the persistence code because the calculation and + * actual size don't match. */ +#if ZEND_DEBUG + uint32_t mem_checksum_skip_list_capacity; +#endif } zend_accel_globals; typedef struct _zend_string_table { diff --git a/ext/opcache/tests/gh8065.inc b/ext/opcache/tests/gh8065.inc new file mode 100644 index 0000000000000..5f4002edd6290 --- /dev/null +++ b/ext/opcache/tests/gh8065.inc @@ -0,0 +1,3 @@ + +--EXPECT-- +Done diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c index dd9d11146a1c2..245451a9e1ec7 100644 --- a/ext/opcache/zend_accelerator_util_funcs.c +++ b/ext/opcache/zend_accelerator_util_funcs.c @@ -353,12 +353,36 @@ unsigned int zend_accel_script_checksum(zend_persistent_script *persistent_scrip mem += (unsigned char*)persistent_script - mem; } - zend_adler32(checksum, mem, persistent_script_check_block_size); + checksum = zend_adler32(checksum, mem, persistent_script_check_block_size); mem += sizeof(*persistent_script); size -= sizeof(*persistent_script); - if (size > 0) { - checksum = zend_adler32(checksum, mem, size); + if (UNEXPECTED(size == 0)) { + return checksum; } + + const zend_accel_skip_list_entry *skip_list_entry = persistent_script->mem_checksum_skip_list; + + size_t offset = 0; + uint32_t next_stop_read; + while ((next_stop_read = skip_list_entry->offset) != 0) { + checksum = zend_adler32(checksum, mem + offset, next_stop_read - offset); + offset = next_stop_read + sizeof(void *); /* skip over the pointer */ + + uint32_t repetitions = skip_list_entry->repetitions; + const uint32_t checked_area_size = skip_list_entry->checked_area_size; + while (repetitions != 0) { + checksum = zend_adler32(checksum, mem + offset, checked_area_size); + offset += checked_area_size + sizeof(void *); /* skip over the pointer */ + repetitions--; + } + + skip_list_entry++; + } + + if (size > offset) { + checksum = zend_adler32(checksum, mem + offset, size - offset); + } + return checksum; } diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 6ca8c46892ae0..fa5979b7c6ced 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -29,6 +29,7 @@ #include "zend_operators.h" #include "zend_interfaces.h" #include "zend_attributes.h" +#include "zend_sort.h" #ifdef HAVE_JIT # include "Optimizer/zend_func_info.h" @@ -87,6 +88,49 @@ static void zend_persist_op_array(zval *zv); static const uint32_t uninitialized_bucket[-HT_MIN_MASK] = {HT_INVALID_IDX, HT_INVALID_IDX}; +static void mem_checksum_skip_list_add(void *p, uint32_t checked_area_size, uint32_t repetitions) +{ + ZEND_ASSERT(ZCG(mem_checksum_skip_list) != NULL); + char *base_ptr = (char *) ZCG(current_persistent_script)->mem + sizeof(zend_persistent_script); + ZEND_ASSERT((char *) p >= base_ptr); +#if ZEND_DEBUG + ZEND_ASSERT(ZCG(mem_checksum_skip_list_count) < ZCG(mem_checksum_skip_list_capacity)); +#endif + zend_accel_skip_list_entry *entry = &ZCG(mem_checksum_skip_list)[ZCG(mem_checksum_skip_list_count)++]; + entry->offset = (char *) p - base_ptr; + entry->checked_area_size = checked_area_size - sizeof(char*); + entry->repetitions = repetitions; +} + +static int mem_checksum_skip_list_compare(const zend_accel_skip_list_entry *l, const zend_accel_skip_list_entry *r) +{ + if (l->offset < r->offset) { + return -1; + } else if (l->offset == r->offset) { + return 0; + } else { + return 1; + } +} + +static void mem_checksum_skip_list_swap(zend_accel_skip_list_entry *l, zend_accel_skip_list_entry *r) +{ + zend_accel_skip_list_entry tmp = *r; + *r = *l; + *l = tmp; +} + +static zend_accel_skip_list_entry *mem_checksum_skip_list_persist(void) +{ + zend_sort(ZCG(mem_checksum_skip_list), ZCG(mem_checksum_skip_list_count), sizeof(zend_accel_skip_list_entry), (compare_func_t) mem_checksum_skip_list_compare, (swap_func_t) mem_checksum_skip_list_swap); + zend_accel_skip_list_entry *result = zend_shared_memdup(ZCG(mem_checksum_skip_list), (ZCG(mem_checksum_skip_list_count) + 1) * sizeof(zend_accel_skip_list_entry)); + zend_accel_skip_list_entry *last_sentinel = &result[ZCG(mem_checksum_skip_list_count)]; + last_sentinel->offset = 0; + last_sentinel->checked_area_size = 0; + last_sentinel->repetitions = 0; + return result; +} + static void zend_hash_persist(HashTable *ht) { uint32_t idx, nIndex; @@ -527,6 +571,13 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc zend_op *end = new_opcodes + op_array->last; int offset = 0; +#ifdef HAVE_JIT + if (ZCG(mem_checksum_skip_list) && JIT_G(on)) { + /* There is already one skip with 0 repetitions, so we have to subtract one */ + mem_checksum_skip_list_add(new_opcodes, sizeof(zend_op), op_array->last - 1); + } +#endif + for (; opline < end ; opline++, offset++) { #if ZEND_USE_ABS_CONST_ADDR if (opline->op1_type == IS_CONST) { @@ -760,6 +811,10 @@ static void zend_persist_class_method(zval *zv, zend_class_entry *ce) return; } op_array = Z_PTR_P(zv) = zend_shared_memdup_put(op_array, sizeof(zend_op_array)); + if (ZCG(mem_checksum_skip_list)) { + /* There is already one skip with 0 repetitions, so we have to subtract one */ + mem_checksum_skip_list_add(&op_array->reserved, sizeof(char*), sizeof(op_array->reserved) / sizeof(op_array->reserved[0]) - 1); + } zend_persist_op_array_ex(op_array, NULL); if (ce->ce_flags & ZEND_ACC_IMMUTABLE) { op_array->fn_flags |= ZEND_ACC_IMMUTABLE; @@ -866,6 +921,9 @@ zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce) ce->ce_flags |= ZEND_ACC_FILE_CACHED; } ce->inheritance_cache = NULL; + if (ZCG(mem_checksum_skip_list)) { + mem_checksum_skip_list_add(&ce->inheritance_cache, 0, 0); + } if (!(ce->ce_flags & ZEND_ACC_CACHED)) { if (ZSTR_HAS_CE_CACHE(ce->name)) { @@ -1282,6 +1340,13 @@ zend_persistent_script *zend_accel_script_persist(zend_persistent_script *script { Bucket *p; + /* The skip list count is still set by the persist_calc routines, which always precedes a call to this function. */ + ZCG(mem_checksum_skip_list) = safe_emalloc(ZCG(mem_checksum_skip_list_count), sizeof(zend_accel_skip_list_entry), 0); +#if ZEND_DEBUG + ZCG(mem_checksum_skip_list_capacity) = ZCG(mem_checksum_skip_list_count); +#endif + ZCG(mem_checksum_skip_list_count) = 0; + script->mem = ZCG(mem); ZEND_ASSERT(((zend_uintptr_t)ZCG(mem) & 0x7) == 0); /* should be 8 byte aligned */ @@ -1346,7 +1411,11 @@ zend_persistent_script *zend_accel_script_persist(zend_persistent_script *script } #endif - script->corrupted = 0; + script->mem_checksum_skip_list = mem_checksum_skip_list_persist(); + efree(ZCG(mem_checksum_skip_list)); + ZCG(mem_checksum_skip_list) = NULL; + + script->corrupted = false; ZCG(current_persistent_script) = NULL; return script; diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 3f79290841a0c..071cfa4f227d5 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -27,6 +27,10 @@ #include "zend_operators.h" #include "zend_attributes.h" +#ifdef HAVE_JIT +#include "jit/zend_jit.h" +#endif + #define ADD_DUP_SIZE(m,s) ZCG(current_persistent_script)->size += zend_shared_memdup_size((void*)m, s) #define ADD_SIZE(m) ZCG(current_persistent_script)->size += ZEND_ALIGNED_SIZE(m) @@ -236,6 +240,11 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array) zend_shared_alloc_register_xlat_entry(op_array->opcodes, op_array->opcodes); ADD_SIZE(sizeof(zend_op) * op_array->last); +#ifdef HAVE_JIT + if (JIT_G(on)) { + ZCG(mem_checksum_skip_list_count)++; + } +#endif if (op_array->filename) { ADD_STRING(op_array->filename); @@ -340,6 +349,7 @@ static void zend_persist_class_method_calc(zval *zv) if (!old_op_array) { ADD_SIZE(sizeof(zend_op_array)); zend_persist_op_array_calc_ex(Z_PTR_P(zv)); + ZCG(mem_checksum_skip_list_count)++; zend_shared_alloc_register_xlat_entry(op_array, Z_PTR_P(zv)); } else { /* If op_array is shared, the function name refcount is still incremented for each use, @@ -399,6 +409,8 @@ void zend_persist_class_entry_calc(zend_class_entry *ce) ADD_SIZE(sizeof(zend_class_entry)); + ZCG(mem_checksum_skip_list_count)++; + if (!(ce->ce_flags & ZEND_ACC_CACHED)) { ADD_INTERNED_STRING(ce->name); if (ce->parent_name && !(ce->ce_flags & ZEND_ACC_LINKED)) { @@ -572,6 +584,9 @@ uint32_t zend_accel_script_persist_calc(zend_persistent_script *new_persistent_s { Bucket *p; + /* Account for the final zero element that acts as a terminator. */ + ZCG(mem_checksum_skip_list_count) = 1; + new_persistent_script->mem = NULL; new_persistent_script->size = 0; new_persistent_script->corrupted = 0; @@ -607,7 +622,9 @@ uint32_t zend_accel_script_persist_calc(zend_persistent_script *new_persistent_s zend_persist_warnings_calc( new_persistent_script->num_warnings, new_persistent_script->warnings); - new_persistent_script->corrupted = 0; + ADD_SIZE(ZCG(mem_checksum_skip_list_count) * sizeof(zend_accel_skip_list_entry)); + + new_persistent_script->corrupted = false; ZCG(current_persistent_script) = NULL;