Skip to content
Merged
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: 1 addition & 1 deletion .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ jobs:
sudo cp ./.github/benchmark-files/php-debugger-on-demand-benchmark.ini /etc/php/${{ matrix.php }}/cli/conf.d
sudo cp ./.github/benchmark-files/php-debugger-on-demand-benchmark.ini /etc/php/${{ matrix.php }}/cgi/conf.d

- name: Copy on-demand php_debugger ini file
- name: Copy connected php_debugger ini file
if: matrix.php_debugger_mode == 'debug-connected'
run: |
sudo cp ./.github/benchmark-files/php-debugger-connected-benchmark.ini /etc/php/${{ matrix.php }}/cli/conf.d
Expand Down
37 changes: 37 additions & 0 deletions src/base/base.c
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,38 @@ function_stack_entry *xdebug_add_stack_frame(zend_execute_data *zdata, zend_op_a
return tmp;
}

static void add_stack_frame_recursively(zend_execute_data *execute_data)

@carlos-granados carlos-granados Apr 28, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a couple of cases (when stopping on xdebug_break or when responding to a xdebug_notify call) we may need to rebuild the stack, because we would not have been keeping it updated because we disabled the debugger as it was not being actively used. This function is used to rebuild the stack from the current execute data recursively

{
zend_op_array *op_array;
int type;
function_stack_entry *fse;

if (execute_data->prev_execute_data) {
add_stack_frame_recursively(execute_data->prev_execute_data);
}
if (execute_data->func) {
op_array = &(execute_data->func->op_array);
type = ZEND_USER_CODE(execute_data->func->type) ? XDEBUG_USER_DEFINED : XDEBUG_BUILT_IN;
fse = xdebug_add_stack_frame(execute_data, op_array, type);
fse->execute_data = execute_data->prev_execute_data;
if (ZEND_CALL_INFO(execute_data) & ZEND_CALL_HAS_SYMBOL_TABLE) {
fse->symbol_table = execute_data->symbol_table;
}
}
}

/*
* This function rebuilds the stack when the debugger is activated after
* being previously deactivated. This rebuild deliberately does not fire entry side-effects,
* like recording eval functions or checking function entry breakpoints
*/
void xdebug_rebuild_stack(void)
{
xdebug_vector_empty(XG_BASE(stack));

add_stack_frame_recursively(EG(current_execute_data));
}

/** Function interceptors and dispatchers to modules ***********************/

static void xdebug_execute_user_code_begin(zend_execute_data *execute_data)
Expand Down Expand Up @@ -501,6 +533,11 @@ static bool should_run_user_handler(zend_execute_data *execute_data)
return false;
}

if (execute_data->func->type == ZEND_EVAL_CODE)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we are within eval code, we let it be handled by the xdebug_execute_ex function as the normal call using the observers may have been disabled. This allows the debugger to register all eval'd functions, report on them and allow setting breakpoints on them

{
return false;
}

return true;
}

Expand Down
2 changes: 2 additions & 0 deletions src/base/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ void xdebug_func_dtor(xdebug_func *elem);
void xdebug_build_fname(xdebug_func *tmp, zend_execute_data *edata);

void xdebug_print_info(void);

void xdebug_rebuild_stack(void);
#endif // __XDEBUG_BASE_H__
11 changes: 9 additions & 2 deletions src/base/ctrl_socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "lib/cmd_parser.h"
#include "lib/log.h"
#include "lib/xml.h"
#include "base/base.h"

#if WIN32
#include <windows.h>
Expand Down Expand Up @@ -223,14 +224,20 @@ CTRL_FUNC(pause)
xdebug_xml_add_text(action, xdstrdup("IDE Connection Signalled"));

XG_DBG(context).do_connect_to_client = 1;

XG_BASE(statement_handler_enabled) = true;
} else {
action = xdebug_xml_node_init("action");
xdebug_xml_add_text(action, xdstrdup("Breakpoint Signalled"));

XG_DBG(context).do_break = 1;
}

if (!XG_BASE(observer_active)) {
xdebug_rebuild_stack();
}

XG_BASE(statement_handler_enabled) = true;
XG_BASE(observer_active) = true;

xdebug_xml_add_child(response, action);
xdebug_xml_add_child(*retval, response);
}
Expand Down
4 changes: 3 additions & 1 deletion src/debugger/com.c
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,9 @@ static void xdebug_init_debugger(void)
xdebug_log_ex(XLOG_CHAN_DEBUG, XLOG_ERR, "NOPERM", "No permission connecting to debugging client (%s). This could be SELinux related.", connection_attempts->d);
}

XG_BASE(statement_handler_enabled) = XG_DBG(remote_connection_enabled);
if (XINI_DBG(on_demand_debugging_enabled)) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only needs to be done for the on_demand mode

XG_BASE(statement_handler_enabled) = XG_DBG(remote_connection_enabled);
}

xdebug_str_free(connection_attempts);
}
Expand Down
21 changes: 20 additions & 1 deletion src/debugger/debugger.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "ext/standard/info.h"
#include "zend_exceptions.h"

#include "base/base.h"
#include "debugger_private.h"
#include "frankenphp.h"
#include "lib/log.h"
Expand Down Expand Up @@ -1231,6 +1232,11 @@ PHP_FUNCTION(xdebug_break)
RETURN_FALSE;
}

if (!XG_BASE(observer_active)) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In xdebug_break we rebuild the stack but we pop the last entry which would correspond to the xdebug_break call and which we don't want to be present

xdebug_rebuild_stack();
xdebug_vector_pop(XG_BASE(stack));
}

xdebug_debug_init_if_requested_on_xdebug_break();

if (!xdebug_is_debug_connection_active()) {
Expand All @@ -1240,6 +1246,9 @@ PHP_FUNCTION(xdebug_break)
XG_DBG(context).do_break = 1;
XG_DBG(context).pending_breakpoint = NULL;

XG_BASE(statement_handler_enabled) = true;
XG_BASE(observer_active) = true;

RETURN_TRUE;
}

Expand All @@ -1258,7 +1267,13 @@ PHP_FUNCTION(xdebug_connect_to_client)

XG_DBG(context).do_connect_to_client = 1;

XG_BASE(statement_handler_enabled) = true;
if (!XG_BASE(observer_active)) {
xdebug_rebuild_stack();
xdebug_vector_pop(XG_BASE(stack));
}

XG_BASE(statement_handler_enabled) = true;
XG_BASE(observer_active) = true;

RETURN_TRUE;
}
Expand All @@ -1283,6 +1298,10 @@ PHP_FUNCTION(xdebug_notify)
return;
}

if (!XG_BASE(observer_active)) {
xdebug_rebuild_stack();
}

fse = xdebug_get_stack_frame(0);

XG_DBG(context).handler->user_notification(
Expand Down
17 changes: 17 additions & 0 deletions src/debugger/handler_dbgp.c
Original file line number Diff line number Diff line change
Expand Up @@ -2493,6 +2493,23 @@ static int xdebug_dbgp_cmdloop(xdebug_con *context, int bail)
free(option);
} while (0 == ret);

if (!XG_DBG(context).do_connect_to_client &&

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If none of these conditions is true it means that the debugger will not stop at any point (except maybe xdebug_break calls) so we can disable most of the debugger functionality

!XG_DBG(context).do_break &&
!XG_DBG(context).do_finish &&
!XG_DBG(context).do_next &&
!XG_DBG(context).do_step &&
!XG_DBG(context).send_notifications &&
!xdebug_lib_start_upon_error() &&
(!XG_DBG(context).line_breakpoints || XG_DBG(context).line_breakpoints->size == 0) &&
(!XG_DBG(context).exception_breakpoints || XG_DBG(context).exception_breakpoints->size == 0) &&
(!XG_DBG(context).function_breakpoints || XG_DBG(context).function_breakpoints->size == 0)
) {
if (!XINI_DBG(on_demand_debugging_enabled)) {
XG_BASE(observer_active) = false;
}
XG_BASE(statement_handler_enabled) = false;
}

if (bail && XG_DBG(status) == DBGP_STATUS_STOPPED) {
_zend_bailout((char*)__FILE__, __LINE__);
}
Expand Down
7 changes: 6 additions & 1 deletion src/lib/vector.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,16 @@ static inline xdebug_vector *xdebug_vector_alloc(size_t element_size, xdebug_vec
return tmp;
}

static inline void xdebug_vector_destroy(xdebug_vector *v)
static inline void xdebug_vector_empty(xdebug_vector *v)
{
while (XDEBUG_VECTOR_COUNT(v)) {
xdebug_vector_pop(v);
}
}

static inline void xdebug_vector_destroy(xdebug_vector *v)
{
xdebug_vector_empty(v);
xdfree(v->data);
xdfree(v);
}
Expand Down
12 changes: 12 additions & 0 deletions tests/debugger/rebuild-stack-after-xdebug-break.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
function inner_function() {
return 42;
}

function outer_function() {
xdebug_break();
$result = inner_function();
return $result;
}

outer_function();
62 changes: 62 additions & 0 deletions tests/debugger/rebuild-stack-after-xdebug-break.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
--TEST--
Stack is correctly rebuilt after xdebug_break() with on_demand_debugging_enabled=0
--SKIPIF--
<?php
require __DIR__ . '/../utils.inc';
check_reqs('dbgp');
?>
--FILE--
<?php
require 'dbgp/dbgpclient.php';

$filename = dirname(__FILE__) . '/rebuild-stack-after-xdebug-break.inc';

$commands = array(
'run',
'stack_get',
'step_into',
'stack_get',
'step_out',
'stack_get',
'detach',
);

$settings = [
'xdebug.mode' => 'debug',
'xdebug.start_with_request' => 'yes',
'xdebug.on_demand_debugging_enabled' => 0
];

dbgpRunFile( $filename, $commands, $settings );
?>
--EXPECTF--
<?xml version="1.0" encoding="iso-8859-1"?>
<init xmlns="urn:debugger_protocol_v1" xmlns:xdebug="https://xdebug.org/dbgp/xdebug" fileuri="file://rebuild-stack-after-xdebug-break.inc" language="PHP" xdebug:language_version="" protocol_version="1.0" appid=""><engine version=""><![CDATA[PHP Debugger]]></engine><author><![CDATA[Derick Rethans]]></author><url><![CDATA[https://xdebug.org]]></url><copyright><![CDATA[Copyright (c) 2002-2099 by Derick Rethans]]></copyright></init>

-> run -i 1
<?xml version="1.0" encoding="iso-8859-1"?>
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="https://xdebug.org/dbgp/xdebug" command="run" transaction_id="1" status="break" reason="ok"><xdebug:message filename="file://rebuild-stack-after-xdebug-break.inc" lineno="8"></xdebug:message></response>

-> stack_get -i 2
<?xml version="1.0" encoding="iso-8859-1"?>
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="https://xdebug.org/dbgp/xdebug" command="stack_get" transaction_id="2"><stack where="outer_function" level="0" type="file" filename="file://rebuild-stack-after-xdebug-break.inc" lineno="8"></stack><stack where="{main}" level="1" type="file" filename="file://rebuild-stack-after-xdebug-break.inc" lineno="12"></stack></response>

-> step_into -i 3
<?xml version="1.0" encoding="iso-8859-1"?>
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="https://xdebug.org/dbgp/xdebug" command="step_into" transaction_id="3" status="break" reason="ok"><xdebug:message filename="file://rebuild-stack-after-xdebug-break.inc" lineno="3"></xdebug:message></response>

-> stack_get -i 4
<?xml version="1.0" encoding="iso-8859-1"?>
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="https://xdebug.org/dbgp/xdebug" command="stack_get" transaction_id="4"><stack where="inner_function" level="0" type="file" filename="file://rebuild-stack-after-xdebug-break.inc" lineno="3"></stack><stack where="outer_function" level="1" type="file" filename="file://rebuild-stack-after-xdebug-break.inc" lineno="8"></stack><stack where="{main}" level="2" type="file" filename="file://rebuild-stack-after-xdebug-break.inc" lineno="12"></stack></response>

-> step_out -i 5
<?xml version="1.0" encoding="iso-8859-1"?>
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="https://xdebug.org/dbgp/xdebug" command="step_out" transaction_id="5" status="break" reason="ok"><xdebug:message filename="file://rebuild-stack-after-xdebug-break.inc" lineno="9"></xdebug:message></response>

-> stack_get -i 6
<?xml version="1.0" encoding="iso-8859-1"?>
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="https://xdebug.org/dbgp/xdebug" command="stack_get" transaction_id="6"><stack where="outer_function" level="0" type="file" filename="file://rebuild-stack-after-xdebug-break.inc" lineno="9"></stack><stack where="{main}" level="1" type="file" filename="file://rebuild-stack-after-xdebug-break.inc" lineno="12"></stack></response>

-> detach -i 7
<?xml version="1.0" encoding="iso-8859-1"?>
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="https://xdebug.org/dbgp/xdebug" command="detach" transaction_id="7" status="stopping" reason="ok"></response>
Loading