Skip to content
Closed
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
40 changes: 36 additions & 4 deletions src/base/base.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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 */
Expand Down
1 change: 1 addition & 0 deletions src/base/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
7 changes: 7 additions & 0 deletions src/debugger/com.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
21 changes: 14 additions & 7 deletions src/debugger/debugger.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;

Expand All @@ -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)) {
Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/debugger/debugger.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
38 changes: 32 additions & 6 deletions src/lib/lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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));
Expand Down
2 changes: 2 additions & 0 deletions src/lib/lib.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
62 changes: 26 additions & 36 deletions xdebug.c
Original file line number Diff line number Diff line change
Expand Up @@ -563,62 +563,52 @@ 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) ||
xdebug_lib_start_upon_error() ||
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;
Expand Down
Loading