From 0e8be64aad9647436ade0f93335266f965399cd1 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Tue, 9 Jan 2024 18:47:06 +0000 Subject: [PATCH] wip --- appsec/tests/integration/gradle/images.gradle | 5 +- ext/ddtrace.c | 11 --- ext/hook/uhook.c | 83 ++++++++++++++++--- ext/hook/uhook.stub.php | 15 +++- ext/hook/uhook_arginfo.h | 14 ++-- .../Roadrunner/RoadrunnerIntegration.php | 6 +- .../install_hook/allow_nested_hook.phpt | 54 ++++++++++++ .../install_hook/override_exception.phpt | 59 +++++++++++++ .../sandbox/install_hook/suppress_call.phpt | 51 ++++++++++++ .../install_hook/suppress_call_jit.phpt | 37 +++++++++ .../interceptor/php7/interceptor.c | 27 +++--- .../jit_utils/jit_blacklist.c | 55 ++++++------ .../jit_utils/jit_blacklist.h | 1 + 13 files changed, 343 insertions(+), 75 deletions(-) create mode 100644 tests/ext/sandbox/install_hook/allow_nested_hook.phpt create mode 100644 tests/ext/sandbox/install_hook/override_exception.phpt create mode 100644 tests/ext/sandbox/install_hook/suppress_call.phpt create mode 100644 tests/ext/sandbox/install_hook/suppress_call_jit.phpt diff --git a/appsec/tests/integration/gradle/images.gradle b/appsec/tests/integration/gradle/images.gradle index 4124849ff2d..29f619e53cd 100644 --- a/appsec/tests/integration/gradle/images.gradle +++ b/appsec/tests/integration/gradle/images.gradle @@ -110,7 +110,6 @@ def buildApache2FpmTask = { String version, String variant -> description = "Build the image for Apache2 + fpm ${version} ${variant}" inputs.dir 'src/docker/apache2-fpm' inputs.dir 'src/docker/fpm-common' - inputs.dir 'src/docker/common' outputs.upToDateWhen { imageUpToDate(inputs, image)() && imageIsNewerThan(image, "$repo:php-$version-$variant") @@ -140,7 +139,6 @@ def buildNginxFpmTask = { String version, String variant -> description = "Build the image for Nginx + fpm ${version} ${variant}" inputs.dir 'src/docker/nginx-fpm' inputs.dir 'src/docker/fpm-common' - inputs.dir 'src/docker/common' outputs.upToDateWhen { imageUpToDate(inputs, image)() && imageIsNewerThan(image, "$repo:php-$version-$variant") @@ -160,6 +158,9 @@ tasks.register('buildAllNginxFpm') { dependsOn "buildNginxFpm-${spec[0]}-${spec[1]}" } } +task buildAll { + dependsOn 'buildAllPhp', 'buildAllApache2Mod', 'buildAllApache2Fpm', 'buildAllNginxFpm' +} def buildPushTask = { String tag, requirement -> def task = tasks.register("pushImage-${tag}", Exec) { diff --git a/ext/ddtrace.c b/ext/ddtrace.c index 4696218edfc..e0f989287af 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -783,11 +783,6 @@ static void dd_disable_if_incompatible_sapi_detected(void) { } } -static void ddtrace_execute_ex(zend_execute_data *execute_data) -{ - execute_ex(execute_data); -} - static PHP_MINIT_FUNCTION(ddtrace) { UNUSED(type); @@ -875,12 +870,6 @@ static PHP_MINIT_FUNCTION(ddtrace) { dd_ip_extraction_startup(); ddtrace_user_request_startup(); - // we need to trigger the slow path in the fcall handler in order to be able to suppress calls - // otherwise OPLINE is not refreshed after calling the begin observers - if (zend_execute_ex == execute_ex) { - zend_execute_ex = ddtrace_execute_ex; - } - return SUCCESS; } diff --git a/ext/hook/uhook.c b/ext/hook/uhook.c index 471bac5bfaf..c2070d5044d 100644 --- a/ext/hook/uhook.c +++ b/ext/hook/uhook.c @@ -218,6 +218,30 @@ static bool dd_uhook_match_filepath(zend_string *file, zend_string *source) { return false; } +#if PHP_VERSION_ID >= 80000 +static void (*orig_zend_interrupt_function)(zend_execute_data *); +ZEND_TLS zend_execute_data *expected_ex; +static void dd_zend_interrupt_function(zend_execute_data *ex) +{ + if (!expected_ex) { + goto call_orig; + } + + if (ex != expected_ex) { + expected_ex = NULL; + goto call_orig; + } + + expected_ex = NULL; + ex->opline = ex->func->op_array.opcodes; + +call_orig: + if (orig_zend_interrupt_function) { + orig_zend_interrupt_function(ex); + } +} +#endif + static bool dd_uhook_begin(zend_ulong invocation, zend_execute_data *execute_data, void *auxiliary, void *dynamic) { dd_uhook_def *def = auxiliary; dd_uhook_dynamic *dyn = dynamic; @@ -260,20 +284,20 @@ static bool dd_uhook_begin(zend_ulong invocation, zend_execute_data *execute_dat if (dyn->hook_data->suppress_call) { if (ZEND_USER_CODE(execute_data->func->type)) { - static union { + static struct { zend_op zop; zval zv; - } retop[] = {[0].zop = + } retop = { .zop = { .opcode = ZEND_RETURN, .op1_type = IS_CONST, - .op1 = {.constant = sizeof(retop[0])}, + .op1 = {.constant = offsetof(typeof(retop), zv)}, .op2_type = IS_UNUSED, }, - [1].zv = {.u1.type_info = IS_NULL}}; + .zv = {.u1.type_info = IS_NULL}}; // the race condition doesn't matter - if (!retop[0].zop.handler) { - zend_vm_set_opcode_handler(&retop[0].zop); + if (!retop.zop.handler) { + zend_vm_set_opcode_handler(&retop.zop); } struct { zend_function new_func; @@ -281,11 +305,24 @@ static bool dd_uhook_begin(zend_ulong invocation, zend_execute_data *execute_dat memcpy(&fs->new_func, execute_data->func, sizeof fs->new_func); fs->orig_func = execute_data->func; fs->new_func.op_array.last = 1; - fs->new_func.op_array.opcodes = (zend_op *)retop; + fs->new_func.op_array.opcodes = &retop.zop; + fs->new_func.op_array.opcodes = &retop.zop; + int zf_rid = zai_get_zend_func_rid(&execute_data->func->op_array); + if (zf_rid >= 0) { + fs->new_func.op_array.reserved[zf_rid] = 0; + } execute_data->func = &fs->new_func; - execute_data->opline = &retop[0].zop; +#if PHP_VERSION_ID >= 80200 + expected_ex = execute_data; + zend_atomic_bool_store_ex(&EG(vm_interrupt), true); +#elif PHP_VERSION_ID >= 80000 + expected_ex = execute_data; + EG(vm_interrupt) = 1; +#else + execute_data->opline = &retop.zop; +#endif } else { - // XXX: not supported yet + // TODO: not supported yet (JIT support appearing problematic) } } @@ -390,7 +427,7 @@ static void dd_uhook_end(zend_ulong invocation, zend_execute_data *execute_data, efree(execute_data->func); execute_data->func = orig_func; } else { - // XXX: not supported yet + // TODO: not supported yet (JIT support appearing problematic) } } @@ -850,6 +887,24 @@ ZEND_METHOD(DDTrace_HookData, overrideReturnValue) { RETURN_TRUE; } +ZEND_METHOD(DDTrace_HookData, disableJitInlining) { + dd_hook_data *hookData = (dd_hook_data *)Z_OBJ_P(ZEND_THIS); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (hookData->execute_data->func->type != ZEND_USER_FUNCTION) { + RETURN_FALSE; + } + +#if ZAI_JIT_BLACKLIST_ACTIVE + zai_jit_blacklist_function_inlining(&hookData->execute_data->func->op_array); +#endif + + RETURN_TRUE; +} + ZEND_METHOD(DDTrace_HookData, suppressCall) { dd_hook_data *hookData = (dd_hook_data *)Z_OBJ_P(ZEND_THIS); @@ -862,7 +917,7 @@ ZEND_METHOD(DDTrace_HookData, suppressCall) { RETURN_TRUE; } -ZEND_METHOD(DDTrace_HookData, enableAdviceOnRecursiveCall) { +ZEND_METHOD(DDTrace_HookData, allowNestedHook) { dd_hook_data *hookData = (dd_hook_data *)Z_OBJ_P(ZEND_THIS); if (zend_parse_parameters_none() == FAILURE) { @@ -975,6 +1030,12 @@ void zai_uhook_minit(int module_number) { efree(closure); EG(objects_store) = objects_store; + + // We must have an interrupt function to be able to suppress calls +#if PHP_VERSION_ID >= 80000 + orig_zend_interrupt_function = zend_interrupt_function; + zend_interrupt_function = &dd_zend_interrupt_function; +#endif } #if PHP_VERSION_ID >= 80000 diff --git a/ext/hook/uhook.stub.php b/ext/hook/uhook.stub.php index 3ea355f9d8b..7a3997d5c2d 100644 --- a/ext/hook/uhook.stub.php +++ b/ext/hook/uhook.stub.php @@ -83,18 +83,25 @@ public function overrideReturnValue(mixed $value): bool; */ public function overrideException(\Throwable|null $exception): bool; + /** + * Disables inlining of this method. + * @return bool true iif we have a user function + */ + public function disableJitInlining(): bool; + /** * Suppresses the call to the hooked function. Must be called within a pre-hook. + * The method disableJitInlining() should be called unconditionally in hooks using this method. * @return bool always 'true' */ public function suppressCall(): bool; /** - * By default, advice is not called when the instrumented function from its advice. - * This method can be used to override this behavior. - * @return bool 'true' if called from the advice, which should always be the case + * By default, hooks are not called if the hooked function is called from the hook. + * This method can be used to override this behavior. The next recursive call will trigger the hook. + * @return bool 'true' if called from the hook, which should always be the case */ - public function enableAdviceOnRecursiveCall(): bool; + public function allowNestedHook(): bool; /** * The name of the file where the function/method call was made from. diff --git a/ext/hook/uhook_arginfo.h b/ext/hook/uhook_arginfo.h index 6955a678658..23c8a49f440 100644 --- a/ext/hook/uhook_arginfo.h +++ b/ext/hook/uhook_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 8fa4825b6822c2bd3c0388cb1e7a5294cb212233 */ + * Stub hash: f0aa3f3648af20b10037d7353c1fe7baa6b8e0d7 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_install_hook, 0, 1, IS_LONG, 0) ZEND_ARG_OBJ_TYPE_MASK(0, target, Closure|Generator, MAY_BE_STRING|MAY_BE_CALLABLE, NULL) @@ -31,10 +31,12 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DDTrace_HookData_overrideE ZEND_ARG_OBJ_INFO(0, exception, Throwable, 1) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DDTrace_HookData_suppressCall, 0, 0, _IS_BOOL, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DDTrace_HookData_disableJitInlining, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() -#define arginfo_class_DDTrace_HookData_enableAdviceOnRecursiveCall arginfo_class_DDTrace_HookData_suppressCall +#define arginfo_class_DDTrace_HookData_suppressCall arginfo_class_DDTrace_HookData_disableJitInlining + +#define arginfo_class_DDTrace_HookData_allowNestedHook arginfo_class_DDTrace_HookData_disableJitInlining ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DDTrace_HookData_getSourceFile, 0, 0, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -47,8 +49,9 @@ ZEND_METHOD(DDTrace_HookData, unlimitedSpan); ZEND_METHOD(DDTrace_HookData, overrideArguments); ZEND_METHOD(DDTrace_HookData, overrideReturnValue); ZEND_METHOD(DDTrace_HookData, overrideException); +ZEND_METHOD(DDTrace_HookData, disableJitInlining); ZEND_METHOD(DDTrace_HookData, suppressCall); -ZEND_METHOD(DDTrace_HookData, enableAdviceOnRecursiveCall); +ZEND_METHOD(DDTrace_HookData, allowNestedHook); ZEND_METHOD(DDTrace_HookData, getSourceFile); @@ -65,8 +68,9 @@ static const zend_function_entry class_DDTrace_HookData_methods[] = { ZEND_ME(DDTrace_HookData, overrideArguments, arginfo_class_DDTrace_HookData_overrideArguments, ZEND_ACC_PUBLIC) ZEND_ME(DDTrace_HookData, overrideReturnValue, arginfo_class_DDTrace_HookData_overrideReturnValue, ZEND_ACC_PUBLIC) ZEND_ME(DDTrace_HookData, overrideException, arginfo_class_DDTrace_HookData_overrideException, ZEND_ACC_PUBLIC) + ZEND_ME(DDTrace_HookData, disableJitInlining, arginfo_class_DDTrace_HookData_disableJitInlining, ZEND_ACC_PUBLIC) ZEND_ME(DDTrace_HookData, suppressCall, arginfo_class_DDTrace_HookData_suppressCall, ZEND_ACC_PUBLIC) - ZEND_ME(DDTrace_HookData, enableAdviceOnRecursiveCall, arginfo_class_DDTrace_HookData_enableAdviceOnRecursiveCall, ZEND_ACC_PUBLIC) + ZEND_ME(DDTrace_HookData, allowNestedHook, arginfo_class_DDTrace_HookData_allowNestedHook, ZEND_ACC_PUBLIC) ZEND_ME(DDTrace_HookData, getSourceFile, arginfo_class_DDTrace_HookData_getSourceFile, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/src/Integrations/Integrations/Roadrunner/RoadrunnerIntegration.php b/src/Integrations/Integrations/Roadrunner/RoadrunnerIntegration.php index 89e7f2a64b1..c28eeb56db5 100644 --- a/src/Integrations/Integrations/Roadrunner/RoadrunnerIntegration.php +++ b/src/Integrations/Integrations/Roadrunner/RoadrunnerIntegration.php @@ -206,16 +206,13 @@ function (HookData $hook) use (&$activeSpan, &$suppressResponse, $integration, $ \DDTrace\close_span(); $activeSpan = null; - // Ideally, I would call the method again to prevent the worker from - // exiting after returning null, but this hook wouldn't be called for such - // a recursive call - $hook->enableAdviceOnRecursiveCall(); if ($recCall++ > 128) { // too many recursive calls. Exit so that the worker can be restarted $hook->overrideReturnValue(null); $this->getWorker()->stop(); return; } + $hook->allowNestedHook(); $newRet = $this->waitRequest(); $hook->overrideReturnValue($newRet); $recCall = 0; @@ -235,6 +232,7 @@ static function ($res) use (&$activeSpan, &$suppressResponse, $thiz) { \DDTrace\install_hook('Spiral\RoadRunner\Http\HttpWorker::respond', function (HookData $hook) use (&$activeSpan, &$suppressResponse) { + $hook->disableJitInlining(); if (!$activeSpan || count($hook->args) < 3) { return; } diff --git a/tests/ext/sandbox/install_hook/allow_nested_hook.phpt b/tests/ext/sandbox/install_hook/allow_nested_hook.phpt new file mode 100644 index 00000000000..16b94b550fb --- /dev/null +++ b/tests/ext/sandbox/install_hook/allow_nested_hook.phpt @@ -0,0 +1,54 @@ +--TEST-- +allowNestedHook() +--FILE-- +allowNestedHook(); + foo(); + } else if ($count == 2) { + foo(); + } +}); + +echo "Before hook:\n"; +foo(); + +DDTrace\remove_hook($hook); + +$count = 0; +$hook = DDTrace\install_hook("foo", function ($hook) {}, function($hook) use (&$count) { + var_dump("hook called: $count"); + $count++; + if ($count == 1) { + $hook->allowNestedHook(); + foo(); + } else if ($count == 2) { + foo(); + } +}); + +echo "After hook:\n"; +foo(); + +?> +--EXPECT-- +Before hook: +string(14) "hook called: 0" +string(14) "hook called: 1" +string(3) "foo" +string(3) "foo" +string(3) "foo" +After hook: +string(3) "foo" +string(14) "hook called: 0" +string(3) "foo" +string(14) "hook called: 1" +string(3) "foo" diff --git a/tests/ext/sandbox/install_hook/override_exception.phpt b/tests/ext/sandbox/install_hook/override_exception.phpt new file mode 100644 index 00000000000..427a6d83c4b --- /dev/null +++ b/tests/ext/sandbox/install_hook/override_exception.phpt @@ -0,0 +1,59 @@ +--TEST-- +overrideException() +--FILE-- +overrideException(new Exception('my exception')); +}); + +echo "With hook:\n"; +try { + var_dump(foo()); +} catch (Exception $e) { + echo "threw ", $e->getMessage(), "\n"; +} + +DDTrace\remove_hook($hook); + +echo "Without hook:\n"; +var_dump(foo()); + + +function foo2() { + throw new Exception('existing exception'); +} +$hook = DDTrace\install_hook("foo2", function($hook) {}, function($hook) { + $hook->overrideException(new Exception('my exception')); +}); + +echo "With hook:\n"; +try { + var_dump(foo2()); +} catch (Exception $e) { + echo "threw ", $e->getMessage(), "\n"; +} + + +DDTrace\remove_hook($hook); + +echo "Without hook:\n"; +try { + var_dump(foo2()); +} catch (Exception $e) { + echo "threw ", $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +With hook: +threw my exception +Without hook: +string(3) "foo" +With hook: +threw my exception +Without hook: +threw existing exception diff --git a/tests/ext/sandbox/install_hook/suppress_call.phpt b/tests/ext/sandbox/install_hook/suppress_call.phpt new file mode 100644 index 00000000000..c7977ff9515 --- /dev/null +++ b/tests/ext/sandbox/install_hook/suppress_call.phpt @@ -0,0 +1,51 @@ +--TEST-- +Suppress function call via suppressCall() +--FILE-- +suppressCall(); +}); + +echo "foo() suppressed\n"; +var_dump(foo()); + +DDTrace\remove_hook($hook); + +echo "foo() not suppressed\n"; +var_dump(foo()); + +function fooStr() : string { + echo 'fooStr() function was called', "\n"; + return 'function was called'; +} + +$hook = DDTrace\install_hook("fooStr", function ($hook) { + $hook->suppressCall(); +}, function ($hook) { + $hook->overrideReturnValue('overriden value'); +}); + + +echo "fooStr() suppressed\n"; +var_dump(fooStr()); + +DDTrace\remove_hook($hook); + +echo "fooStr() not suppressed\n"; +var_dump(fooStr()); +?> +--EXPECT-- +foo() suppressed +NULL +foo() not suppressed +string(19) "function was called" +fooStr() suppressed +string(15) "overriden value" +fooStr() not suppressed +fooStr() function was called +string(19) "function was called" + diff --git a/tests/ext/sandbox/install_hook/suppress_call_jit.phpt b/tests/ext/sandbox/install_hook/suppress_call_jit.phpt new file mode 100644 index 00000000000..8cc4fa096e9 --- /dev/null +++ b/tests/ext/sandbox/install_hook/suppress_call_jit.phpt @@ -0,0 +1,37 @@ +--TEST-- +suppressCall() works with JIT +--SKIPIF-- + + += 80000 && PHP_VERSION_ID < 80100 && getenv('USE_ZEND_ALLOC') === '0' && !getenv("SKIP_ASAN")) die('skip: On php 8.0 we use heuristics to match the pointer. Valgrind does not have a pointer layout matching our assumptions and will gracefully fail the test.'); ?> +--INI-- +opcache.enable=1 +opcache.enable_cli = 1 +opcache.jit_buffer_size=512M +opcache.jit=1255 +zend_extension=opcache.so +--FILE-- +disableJitInlining(); + $hook->suppressCall(); + } +); + +echo "With hook\n"; +foo(); + +DDTrace\remove_hook($hook); + +echo "Without hook\n"; +foo(); + +--EXPECT-- +With hook +Without hook +string(6) "called" diff --git a/zend_abstract_interface/interceptor/php7/interceptor.c b/zend_abstract_interface/interceptor/php7/interceptor.c index 6b0663198ec..122b195d74d 100644 --- a/zend_abstract_interface/interceptor/php7/interceptor.c +++ b/zend_abstract_interface/interceptor/php7/interceptor.c @@ -172,24 +172,29 @@ static inline int zai_interceptor_ext_nop_handler_no_prev(zend_execute_data *exe frame_memory.execute_data = execute_data; frame_memory.implicit = false; zai_hook_memory_table_insert(execute_data, &frame_memory); + + if (&execute_data->func->op_array != op_array) { + // the code was changed, so instead of executing the original handler of + // opline->opcode (gotten via zend_vm_get_opcode_handler_func), + // we return ZEND_USER_OPCODE_CONTINUE so that user opcode handler + // of the new execute_data->opline is executed + return ZEND_USER_OPCODE_CONTINUE; + } } } } - if (&execute_data->func->op_array == op_array) { - return ZEND_USER_OPCODE_DISPATCH; - } else { - // the code was changed, so instead of executing the original handler of - // opline->opcode (gotten via zend_vm_get_opcode_handler_func), - // we return ZEND_USER_OPCODE_CONTINUE so that user opcode handler - // of the new execute_data->opline is executed - return ZEND_USER_OPCODE_CONTINUE; - } + return ZEND_USER_OPCODE_DISPATCH; } static int zai_interceptor_ext_nop_handler(zend_execute_data *execute_data) { - zai_interceptor_ext_nop_handler_no_prev(execute_data); - return prev_ext_nop_handler(execute_data); + int our_ret = zai_interceptor_ext_nop_handler_no_prev(execute_data); + int their_ret = prev_ext_nop_handler(execute_data); + zend_op_array *prev_op_array = &execute_data->func->op_array; + if (their_ret == ZEND_USER_OPCODE_DISPATCH && our_ret == ZEND_USER_OPCODE_CONTINUE && &execute_data->func->op_array == prev_op_array) { + return our_ret; + } + return their_ret; } static inline zval *zai_interceptor_get_zval_ptr(const zend_op *opline, int op_type, const znode_op *node, const zend_execute_data *execute_data) { diff --git a/zend_abstract_interface/jit_utils/jit_blacklist.c b/zend_abstract_interface/jit_utils/jit_blacklist.c index 7f6ded56cdb..b1fcd1a6511 100644 --- a/zend_abstract_interface/jit_utils/jit_blacklist.c +++ b/zend_abstract_interface/jit_utils/jit_blacklist.c @@ -129,40 +129,41 @@ static inline bool check_pointer_near(void *a, void *b) { } #endif -void zai_jit_blacklist_function_inlining(zend_op_array *op_array) { - if (zend_func_info_rid < 0) { +int zai_get_zend_func_rid(zend_op_array *op_array) { #if PHP_VERSION_ID < 80100 - if (zend_func_info_rid == -2) { - if (!opcache_handle) { - zai_jit_func_info_rid = -1; - } else { - // On PHP 8.0 we impossibly can get hold of zend_func_info_rid. - // We determine it on our own heuristically, assuming: - // a) The zend_func_info_rid is allocated in shared memory. - // b) The op_array data is also allocated in shared memory, and thus relatively near. - // c) The first matching pointer in op_array->reserved is the zend_func_info_rid. - // d) "Normal" memory, like the VM stack is far away - - if (check_pointer_near(op_array->arg_info, EG(vm_stack))) { - // This function does not seem JITted - return; - } + if (zend_func_info_rid == -2) { + if (!opcache_handle) { + zai_jit_func_info_rid = -1; + } else { + // On PHP 8.0 we impossibly can get hold of zend_func_info_rid. + // We determine it on our own heuristically, assuming: + // a) The zend_func_info_rid is allocated in shared memory. + // b) The op_array data is also allocated in shared memory, and thus relatively near. + // c) The first matching pointer in op_array->reserved is the zend_func_info_rid. + // d) "Normal" memory, like the VM stack is far away + + if (check_pointer_near(op_array->arg_info, EG(vm_stack))) { + // This function does not seem JITted + return -1; + } - for (int i = 0; i < ZEND_MAX_RESERVED_RESOURCES; ++i) { - if (check_pointer_near(op_array->reserved, op_array->arg_info)) { - zend_func_info_rid = i; - break; - } + for (int i = 0; i < ZEND_MAX_RESERVED_RESOURCES; ++i) { + if (check_pointer_near(op_array->reserved, op_array->arg_info)) { + return (zend_func_info_rid = i); } } } - if (zend_func_info_rid < 0) { - return; - } -#else - return; + } #endif + (void)op_array; + return zend_func_info_rid; +} + +void zai_jit_blacklist_function_inlining(zend_op_array *op_array) { + if (zai_get_zend_func_rid(op_array) < 0) { + return; } + // now in PHP < 8.1, zend_func_info_rid is set zend_jit_op_array_trace_extension *jit_extension = (zend_jit_op_array_trace_extension *)ZEND_FUNC_INFO(op_array); if (!jit_extension) { diff --git a/zend_abstract_interface/jit_utils/jit_blacklist.h b/zend_abstract_interface/jit_utils/jit_blacklist.h index b1e1e77a24f..29e781b8a87 100644 --- a/zend_abstract_interface/jit_utils/jit_blacklist.h +++ b/zend_abstract_interface/jit_utils/jit_blacklist.h @@ -14,6 +14,7 @@ #if ZAI_JIT_BLACKLIST_ACTIVE void zai_jit_minit(void); +int zai_get_zend_func_rid(zend_op_array *op_array); void zai_jit_blacklist_function_inlining(zend_op_array *op_array); #endif