diff --git a/src/base/base.c b/src/base/base.c index 311217d..586a167 100644 --- a/src/base/base.c +++ b/src/base/base.c @@ -1056,7 +1056,14 @@ static xdebug_vector *find_stack_for_fiber(zend_string *fiber_key, zend_fiber_co static void xdebug_fiber_switch_observer(zend_fiber_context *from, zend_fiber_context *to) { xdebug_vector *current_stack; - zend_string *to_key = create_key_for_fiber(to); + zend_string *to_key; + + /* Skip fiber tracking when dormant (fiber_stacks not allocated) */ + if (!XG_BASE(fiber_stacks)) { + return; + } + + to_key = create_key_for_fiber(to); if (from->status == ZEND_FIBER_STATUS_DEAD) { zend_string *from_key = create_key_for_fiber(from); @@ -1205,6 +1212,27 @@ void xdebug_base_post_startup() zend_compile_file = xdebug_compile_file; } +void xdebug_base_rinit_dormant() +{ + /* Minimal init when no debug session will happen. + * Skip heavy allocations (fiber_stacks, stack, filters, control socket). + * NULL pointers are handled by cleanup code. */ + XG_BASE(fiber_stacks) = NULL; + XG_BASE(stack) = NULL; + XG_BASE(in_debug_info) = 0; + XG_BASE(prev_memory) = 0; + XG_BASE(function_count) = -1; + XG_BASE(last_eval_statement) = NULL; + XG_BASE(last_exception_trace) = NULL; + XG_BASE(statement_handler_enabled) = false; + XG_BASE(in_var_serialisation) = 0; + XG_BASE(in_execution) = 1; + XG_BASE(observer_active) = 0; + XG_BASE(needs_debug_init) = 0; + XG_BASE(filter_type_stack) = XDEBUG_FILTER_NONE; + XG_BASE(filters_stack) = NULL; +} + void xdebug_base_rinit() { /* Hack: We check for a soap header here, if that's existing, we don't use @@ -1287,8 +1315,10 @@ void xdebug_base_rinit() void xdebug_base_post_deactivate() { - xdebug_hash_destroy(XG_BASE(fiber_stacks)); - XG_BASE(fiber_stacks) = NULL; + if (XG_BASE(fiber_stacks)) { + xdebug_hash_destroy(XG_BASE(fiber_stacks)); + XG_BASE(fiber_stacks) = NULL; + } XG_BASE(stack) = NULL; XG_BASE(in_debug_info) = 0; @@ -1303,7 +1333,9 @@ void xdebug_base_post_deactivate() } /* filters */ - xdebug_llist_destroy(XG_BASE(filters_stack), NULL); + if (XG_BASE(filters_stack)) { + xdebug_llist_destroy(XG_BASE(filters_stack), NULL); + } #if HAVE_XDEBUG_CONTROL_SOCKET_SUPPORT /* Close Down Control Socket */ diff --git a/src/base/base.h b/src/base/base.h index 597f34c..4cb4243 100644 --- a/src/base/base.h +++ b/src/base/base.h @@ -22,6 +22,7 @@ void xdebug_base_mshutdown(); void xdebug_base_post_startup(); +void xdebug_base_rinit_dormant(); void xdebug_base_rinit(); void xdebug_base_post_deactivate(); void xdebug_base_rshutdown(); diff --git a/src/debugger/com.c b/src/debugger/com.c index cc4dcae..36a9eeb 100644 --- a/src/debugger/com.c +++ b/src/debugger/com.c @@ -608,6 +608,13 @@ static void xdebug_init_debugger() { xdebug_str *connection_attempts = xdebug_str_new(); + /* Lazy init: allocate breakable_lines_map if deferred from RINIT + * (happens when debug session is initiated mid-request via xdebug_break() + * or start_upon_error, after the early connection check failed) */ + if (!XG_DBG(breakable_lines_map)) { + xdebug_debugger_rinit(); + } + /* Get handler from mode */ XG_DBG(context).handler = &xdebug_handler_dbgp; diff --git a/src/debugger/debugger.c b/src/debugger/debugger.c index f51480d..4e8d5fa 100644 --- a/src/debugger/debugger.c +++ b/src/debugger/debugger.c @@ -874,13 +874,11 @@ void xdebug_debugger_minfo(void) php_info_print_table_end(); } -void xdebug_debugger_rinit(void) +void xdebug_debugger_rinit_early(void) { char *idekey; - xdebug_disable_opcache_optimizer(); - - /* Get the ide key for this session */ + /* Get the ide key for this session (needed for early connection check) */ XG_DBG(ide_key) = NULL; idekey = xdebug_debugger_get_ide_key(); if (idekey && *idekey) { @@ -919,7 +917,7 @@ void xdebug_debugger_rinit(void) XG_DBG(breakpoints_allowed) = 1; XG_DBG(suppress_return_value_step) = 0; XG_DBG(detached) = 0; - XG_DBG(breakable_lines_map) = xdebug_hash_alloc(2048, (xdebug_hash_dtor_t) xdebug_line_list_dtor); + XG_DBG(breakable_lines_map) = NULL; XG_DBG(function_count) = 0; XG_DBG(class_count) = 0; @@ -940,6 +938,13 @@ void xdebug_debugger_rinit(void) XG_DBG(context).detached_message = NULL; } +void xdebug_debugger_rinit(void) +{ + xdebug_disable_opcache_optimizer(); + + XG_DBG(breakable_lines_map) = xdebug_hash_alloc(2048, (xdebug_hash_dtor_t) xdebug_line_list_dtor); +} + void xdebug_debugger_post_deactivate(void) { if (XG_DBG(remote_connection_enabled)) { @@ -960,8 +965,10 @@ void xdebug_debugger_post_deactivate(void) XG_DBG(context).list.last_filename = NULL; } - xdebug_hash_destroy(XG_DBG(breakable_lines_map)); - XG_DBG(breakable_lines_map) = NULL; + if (XG_DBG(breakable_lines_map)) { + xdebug_hash_destroy(XG_DBG(breakable_lines_map)); + XG_DBG(breakable_lines_map) = NULL; + } if (XG_DBG(context).connected_hostname) { xdfree(XG_DBG(context).connected_hostname); diff --git a/src/debugger/debugger.h b/src/debugger/debugger.h index 7d4df2f..8bf3044 100644 --- a/src/debugger/debugger.h +++ b/src/debugger/debugger.h @@ -90,6 +90,7 @@ void xdebug_debugger_zend_startup(void); void xdebug_debugger_zend_shutdown(void); void xdebug_debugger_minit(void); void xdebug_debugger_minfo(void); +void xdebug_debugger_rinit_early(void); void xdebug_debugger_rinit(void); void xdebug_debugger_post_deactivate(void); diff --git a/src/lib/lib.c b/src/lib/lib.c index a9b3508..c960254 100644 --- a/src/lib/lib.c +++ b/src/lib/lib.c @@ -80,10 +80,30 @@ void xdebug_library_mshutdown(void) xdebug_set_free(XG_LIB(opcode_handlers_set)); } -void xdebug_library_rinit(void) +void xdebug_library_rinit_log(void) { + /* Open log and diagnosis buffer early, before trigger/connection check. + * This ensures all log messages during trigger evaluation and + * connection attempts are captured. */ XG_LIB(diagnosis_buffer) = xdebug_str_new(); xdebug_open_log(); +} + +void xdebug_library_rinit_dormant(void) +{ + /* Minimal init when no debug session will happen. + * Log and diagnosis_buffer already opened by rinit_log(). + * Skip heavy allocations (headers, trait_location_map, path_mapping). */ + XG_LIB(headers) = NULL; + XG_LIB(dumped) = 0; + XG_LIB(do_collect_errors) = 0; + XG_LIB(trait_location_map) = NULL; + XG_LIB(path_mapping_information) = NULL; +} + +void xdebug_library_rinit(void) +{ + /* Log and diagnosis_buffer already opened by rinit_log(). */ XG_LIB(headers) = xdebug_llist_alloc(xdebug_llist_string_dtor); @@ -118,14 +138,20 @@ void xdebug_library_rinit(void) void xdebug_library_post_deactivate(void) { /* Clean up collected headers */ - xdebug_llist_destroy(XG_LIB(headers), NULL); - XG_LIB(headers) = NULL; + if (XG_LIB(headers)) { + xdebug_llist_destroy(XG_LIB(headers), NULL); + XG_LIB(headers) = NULL; + } - xdebug_hash_destroy(XG_LIB(trait_location_map)); + if (XG_LIB(trait_location_map)) { + xdebug_hash_destroy(XG_LIB(trait_location_map)); + } xdebug_close_log(); - xdebug_str_free(XG_LIB(diagnosis_buffer)); - XG_LIB(diagnosis_buffer) = NULL; + if (XG_LIB(diagnosis_buffer)) { + xdebug_str_free(XG_LIB(diagnosis_buffer)); + XG_LIB(diagnosis_buffer) = NULL; + } if (XG_LIB(path_mapping_information)) { xdebug_path_maps_dtor(XG_LIB(path_mapping_information)); diff --git a/src/lib/lib.h b/src/lib/lib.h index 962a70a..87dff56 100644 --- a/src/lib/lib.h +++ b/src/lib/lib.h @@ -230,6 +230,8 @@ void xdebug_library_zend_startup(void); void xdebug_library_zend_shutdown(void); void xdebug_library_minit(void); void xdebug_library_mshutdown(void); +void xdebug_library_rinit_log(void); +void xdebug_library_rinit_dormant(void); void xdebug_library_rinit(void); void xdebug_library_post_deactivate(void); diff --git a/xdebug.c b/xdebug.c index b223a98..f2702c5 100644 --- a/xdebug.c +++ b/xdebug.c @@ -563,36 +563,27 @@ PHP_RINIT_FUNCTION(xdebug) this can override the idekey if one is set */ xdebug_env_config(); - xdebug_library_rinit(); + /* Open log and diagnosis buffer early — needed for trigger check + * and connection logging. Remaining library init is deferred. */ + xdebug_library_rinit_log(); if (XDEBUG_MODE_IS(XDEBUG_MODE_STEP_DEBUG)) { - xdebug_debugger_rinit(); + /* Minimal debugger setup: IDE key, no_exec check, context init. + * Heavy allocations are deferred until we know a debugger will connect. */ + xdebug_debugger_rinit_early(); if (xdebug_debugger_bailout_if_no_exec_requested()) { zend_bailout(); } - } - - xdebug_init_auto_globals(); - /* Early debug init: attempt connection at RINIT so observer_active is set - * before any user code runs. This allows xdebug_observer_init to return - * {NULL, NULL} for functions first-called when no debugger is connected. */ - xdebug_base_rinit(); + /* Initialize auto globals early — needed for trigger check + * (xdebug_lib_find_in_globals accesses _GET, _POST, _COOKIE, _ENV) */ + xdebug_init_auto_globals(); - if (XDEBUG_MODE_IS(XDEBUG_MODE_STEP_DEBUG)) { - /* Check early if debugging could be requested this request. - * For start_with_request=default (trigger mode), check if any - * trigger is present. If not, disable all heavy hooks for - * near-zero overhead. The actual connection happens on first - * function call if triggers are present. */ /* Check if debugging could be requested this request. * For trigger/default mode: check triggers, cookies, env vars. * For yes mode: always expect a connection. - * For no mode: no debugging will happen. - * Note: xdebug_break() can initiate connections without triggers, - * but it handles re-enabling the observer itself. */ - /* Respect start_with_request=no and XDEBUG_IGNORE */ + * For no mode: no debugging will happen. */ int debug_requested = !xdebug_lib_never_start_with_request() && !xdebug_should_ignore() && ( xdebug_lib_start_with_request(XDEBUG_MODE_STEP_DEBUG) || xdebug_lib_start_with_trigger(XDEBUG_MODE_STEP_DEBUG, NULL) || @@ -600,25 +591,24 @@ PHP_RINIT_FUNCTION(xdebug) getenv("XDEBUG_SESSION_START") != NULL ); - if (debug_requested) { - /* Debug session requested: check if a client is actually listening - * before enabling expensive EXT_STMT opcodes. This avoids ~2x - * overhead when triggers are present but no IDE is connected. */ - if (xdebug_early_connect_to_client()) { - CG(compiler_options) = CG(compiler_options) | ZEND_COMPILE_EXTENDED_STMT; - } else { - /* Trigger present but no client listening — stay dormant */ - XG_BASE(observer_active) = 0; - XG_BASE(statement_handler_enabled) = false; - } + if (debug_requested && xdebug_early_connect_to_client()) { + /* Client connected — do full init */ + xdebug_library_rinit(); + xdebug_debugger_rinit(); + xdebug_base_rinit(); + CG(compiler_options) = CG(compiler_options) | ZEND_COMPILE_EXTENDED_STMT; } else { - /* No debug trigger: disable all heavy hooks for near-zero overhead. - * Note: xdebug_break() jit mode won't have full stepping support - * without EXT_STMT opcodes. Use start_with_request=yes or a trigger - * for full debugging support. */ - XG_BASE(observer_active) = 0; - XG_BASE(statement_handler_enabled) = false; + /* Dormant: no debugger will connect this request. + * Skip heavy allocations (library, debugger, base init). + * Set minimal flags for correctness. */ + xdebug_library_rinit_dormant(); + xdebug_base_rinit_dormant(); } + } else { + /* Non-debug mode (shouldn't happen since profiler/coverage are stripped, + * but handle gracefully) */ + xdebug_library_rinit(); + xdebug_base_rinit(); } return SUCCESS;