1 /*
2 +----------------------------------------------------------------------+
3 | Xdebug |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2002-2021 Derick Rethans |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 1.01 of the Xdebug license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available at through the world-wide-web at |
10 | https://xdebug.org/license.php |
11 | If you did not receive a copy of the Xdebug license and are unable |
12 | to obtain it through the world-wide-web, please send a note to |
13 | derick@xdebug.org so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 */
16
17 #include "php_xdebug.h"
18
19 #include "ext/standard/info.h"
20 #include "zend_exceptions.h"
21
22 #include "debugger_private.h"
23 #include "lib/var.h"
24
25 extern ZEND_DECLARE_MODULE_GLOBALS(xdebug);
26
27 static size_t (*xdebug_orig_ub_write)(const char *string, size_t len);
28 static size_t xdebug_ub_write(const char *string, size_t length);
29
30 static void xdebug_line_list_dtor(xdebug_lines_list *line_list);
31
xdebug_init_debugger_globals(xdebug_debugger_globals_t * xg)32 void xdebug_init_debugger_globals(xdebug_debugger_globals_t *xg)
33 {
34 xg->breakpoint_count = 0;
35 xg->ide_key = NULL;
36 xg->stdout_mode = 0;
37 xg->no_exec = 0;
38 xg->context.program_name = NULL;
39 xg->context.list.last_filename = NULL;
40 xg->context.list.last_line = 0;
41 xg->context.do_break = 0;
42 xg->context.pending_breakpoint = NULL;
43 xg->context.do_step = 0;
44 xg->context.do_next = 0;
45 xg->context.do_finish = 0;
46 xg->context.do_connect_to_client = 0;
47
48 xg->remote_connection_enabled = 0;
49 xg->remote_connection_pid = 0;
50 xg->breakpoints_allowed = 0;
51
52 /* Capturing output */
53 if (sapi_module.ub_write != xdebug_ub_write) {
54 xdebug_orig_ub_write = sapi_module.ub_write;
55 sapi_module.ub_write = xdebug_ub_write;
56 }
57
58 /* Statistics and diagnostics */
59 xg->context.connected_hostname = NULL;
60 xg->context.connected_port = 0;
61 xg->context.detached_message = NULL;
62 }
63
xdebug_debugger_get_ide_key(void)64 static char *xdebug_debugger_get_ide_key(void)
65 {
66 char *ide_key;
67
68 ide_key = XINI_DBG(ide_key_setting);
69 if (ide_key && *ide_key) {
70 return ide_key;
71 }
72
73 ide_key = getenv("DBGP_IDEKEY");
74 if (ide_key && *ide_key) {
75 return ide_key;
76 }
77
78 return NULL;
79 }
80
xdebug_debugger_reset_ide_key(char * envval)81 void xdebug_debugger_reset_ide_key(char *envval)
82 {
83 if (XG_DBG(ide_key)) {
84 xdfree(XG_DBG(ide_key));
85 }
86 XG_DBG(ide_key) = xdstrdup(envval);
87 }
88
xdebug_debugger_bailout_if_no_exec_requested(void)89 int xdebug_debugger_bailout_if_no_exec_requested(void)
90 {
91 /* We need to do this first before the executable clauses are called */
92 if (XG_DBG(no_exec) == 1) {
93 php_printf("DEBUG SESSION ENDED");
94 return 1;
95 }
96 return 0;
97 }
98
register_compiled_variables(void)99 static void register_compiled_variables(void)
100 {
101 function_stack_entry *loop_fse = XDEBUG_VECTOR_TAIL(XG_BASE(stack));
102 int i;
103
104 for (i = 0; i < XDEBUG_VECTOR_COUNT(XG_BASE(stack)); i++, loop_fse--) {
105 if (loop_fse->declared_vars) {
106 continue;
107 }
108
109 if (loop_fse->user_defined == XDEBUG_BUILT_IN) {
110 continue;
111 }
112
113 xdebug_lib_register_compiled_variables(loop_fse, loop_fse->op_array);
114 }
115 }
116
117
xdebug_debugger_set_program_name(zend_string * filename)118 void xdebug_debugger_set_program_name(zend_string *filename)
119 {
120 if (!XG_DBG(context).program_name) {
121 XG_DBG(context).program_name = zend_string_copy(filename);
122 }
123 }
124
125 /* Remote debugger helper functions */
xdebug_handle_hit_value(xdebug_brk_info * brk_info)126 static int xdebug_handle_hit_value(xdebug_brk_info *brk_info)
127 {
128 /* If this is a temporary breakpoint, disable the breakpoint */
129 if (brk_info->temporary) {
130 brk_info->disabled = 1;
131 }
132
133 /* Increase hit counter */
134 brk_info->hit_count++;
135
136 /* If the hit_value is 0, the condition check is disabled */
137 if (!brk_info->hit_value) {
138 return 1;
139 }
140
141 switch (brk_info->hit_condition) {
142 case XDEBUG_HIT_GREATER_EQUAL:
143 if (brk_info->hit_count >= brk_info->hit_value) {
144 return 1;
145 }
146 break;
147 case XDEBUG_HIT_EQUAL:
148 if (brk_info->hit_count == brk_info->hit_value) {
149 return 1;
150 }
151 break;
152 case XDEBUG_HIT_MOD:
153 if (brk_info->hit_count % brk_info->hit_value == 0) {
154 return 1;
155 }
156 break;
157 case XDEBUG_HIT_DISABLED:
158 return 1;
159 break;
160 }
161 return 0;
162 }
163
xdebug_do_eval(char * eval_string,zval * ret_zval)164 int xdebug_do_eval(char *eval_string, zval *ret_zval)
165 {
166 #if PHP_VERSION_ID < 80000
167 int old_track_errors;
168 #endif
169 int res = 1;
170 zend_execute_data *original_execute_data = EG(current_execute_data);
171 int original_no_extensions = EG(no_extensions);
172 zend_object *original_exception = EG(exception);
173 JMP_BUF *original_bailout = EG(bailout);
174
175 /* Remember error reporting level and track errors */
176 XG_BASE(error_reporting_override) = EG(error_reporting);
177 XG_BASE(error_reporting_overridden) = 1;
178 #if PHP_VERSION_ID < 80000
179 old_track_errors = PG(track_errors);
180 #endif
181 EG(error_reporting) = 0;
182 #if PHP_VERSION_ID < 80000
183 PG(track_errors) = 0;
184 #endif
185
186 XG_DBG(context).inhibit_notifications = 1;
187 XG_DBG(breakpoints_allowed) = 0;
188
189 /* Reset exception in case we're triggered while being in xdebug_throw_exception_hook */
190 EG(exception) = NULL;
191
192 /* Do evaluation */
193 zend_first_try {
194 res = (zend_eval_string(eval_string, ret_zval, (char*) "xdebug://debug-eval") == SUCCESS);
195 } zend_end_try();
196
197 /* FIXME: Bubble up exception message to DBGp return packet */
198 if (EG(exception)) {
199 #if PHP_VERSION_ID >= 80000
200 if (!res) {
201 zend_clear_exception();
202 }
203 #endif
204 res = 0;
205 }
206
207 /* Clean up */
208 EG(error_reporting) = XG_BASE(error_reporting_override);
209 XG_BASE(error_reporting_overridden) = 0;
210 #if PHP_VERSION_ID < 80000
211 PG(track_errors) = old_track_errors;
212 #endif
213 XG_DBG(breakpoints_allowed) = 1;
214 XG_DBG(context).inhibit_notifications = 0;
215
216 EG(current_execute_data) = original_execute_data;
217 EG(no_extensions) = original_no_extensions;
218 EG(exception) = original_exception;
219 EG(bailout) = original_bailout;
220
221 return res;
222 }
223
xdebug_debugger_statement_call(zend_string * filename,int lineno)224 void xdebug_debugger_statement_call(zend_string *filename, int lineno)
225 {
226 xdebug_llist_element *le;
227 xdebug_brk_info *extra_brk_info;
228 function_stack_entry *fse;
229 int level = 0;
230 int func_nr = 0;
231
232 if (XG_DBG(context).do_connect_to_client) {
233 XG_DBG(context).do_connect_to_client = 0;
234
235 if (!xdebug_is_debug_connection_active()) {
236 xdebug_debug_init_if_requested_on_xdebug_break();
237 register_compiled_variables();
238 }
239 }
240
241 if (xdebug_is_debug_connection_active()) {
242 if (XG_DBG(context).do_break) {
243 xdebug_brk_info *brk_info = XG_DBG(context).pending_breakpoint;
244
245 XG_DBG(context).do_break = 0;
246 XG_DBG(context).pending_breakpoint = NULL;
247
248 if (!XG_DBG(context).handler->remote_breakpoint(&(XG_DBG(context)), XG_BASE(stack), filename, lineno, XDEBUG_BREAK, NULL, 0, NULL, brk_info)) {
249 xdebug_mark_debug_connection_not_active();
250 return;
251 }
252 }
253
254 /* Get latest stack level and function number */
255 if (XG_BASE(stack) && XDEBUG_VECTOR_TAIL(XG_BASE(stack))) {
256 fse = XDEBUG_VECTOR_TAIL(XG_BASE(stack));
257 level = fse->level;
258 func_nr = fse->function_nr;
259 } else {
260 level = 0;
261 func_nr = 0;
262 }
263
264 /* Check for "finish" */
265 if (
266 XG_DBG(context).do_finish &&
267 (
268 (level < XG_DBG(context).finish_level) ||
269 ((level == XG_DBG(context).finish_level) && (func_nr > XG_DBG(context).finish_func_nr))
270 )
271 ) {
272 XG_DBG(context).do_finish = 0;
273
274 if (!XG_DBG(context).handler->remote_breakpoint(&(XG_DBG(context)), XG_BASE(stack), filename, lineno, XDEBUG_STEP, NULL, 0, NULL, NULL)) {
275 xdebug_mark_debug_connection_not_active();
276 return;
277 }
278 return;
279 }
280
281 /* Check for "next" */
282 if (XG_DBG(context).do_next && XG_DBG(context).next_level >= level) {
283 XG_DBG(context).do_next = 0;
284
285 if (!XG_DBG(context).handler->remote_breakpoint(&(XG_DBG(context)), XG_BASE(stack), filename, lineno, XDEBUG_STEP, NULL, 0, NULL, NULL)) {
286 xdebug_mark_debug_connection_not_active();
287 return;
288 }
289 return;
290 }
291
292 /* Check for "step" */
293 if (XG_DBG(context).do_step) {
294 XG_DBG(context).do_step = 0;
295
296 if (!XG_DBG(context).handler->remote_breakpoint(&(XG_DBG(context)), XG_BASE(stack), filename, lineno, XDEBUG_STEP, NULL, 0, NULL, NULL)) {
297 xdebug_mark_debug_connection_not_active();
298 return;
299 }
300 return;
301 }
302
303 if (XG_DBG(context).line_breakpoints) {
304 int break_ok, res;
305 zval retval;
306
307 for (le = XDEBUG_LLIST_HEAD(XG_DBG(context).line_breakpoints); le != NULL; le = XDEBUG_LLIST_NEXT(le)) {
308 extra_brk_info = XDEBUG_LLIST_VALP(le);
309
310 if (XG_DBG(context).handler->break_on_line(&(XG_DBG(context)), extra_brk_info, filename, lineno)) {
311 break_ok = 1; /* Breaking is allowed by default */
312
313 /* Check if we have a condition set for it */
314 if (extra_brk_info->condition) {
315 /* If there is a condition, we disable breaking by
316 * default and only enabled it when the code evaluates
317 * to TRUE */
318 break_ok = 0;
319
320 /* Remember error reporting level */
321 res = xdebug_do_eval(extra_brk_info->condition, &retval);
322 if (res) {
323 break_ok = Z_TYPE(retval) == IS_TRUE;
324 zval_dtor(&retval);
325 }
326 }
327 if (break_ok && xdebug_handle_hit_value(extra_brk_info)) {
328 if (!XG_DBG(context).handler->remote_breakpoint(&(XG_DBG(context)), XG_BASE(stack), filename, lineno, XDEBUG_BREAK, NULL, 0, NULL, extra_brk_info)) {
329 xdebug_mark_debug_connection_not_active();
330 break;
331 }
332 return;
333 }
334 }
335 }
336 }
337 }
338 }
339
340 #if PHP_VERSION_ID >= 80000
xdebug_debugger_throw_exception_hook(zend_object * exception,zval * file,zval * line,zval * code,char * code_str,zval * message)341 void xdebug_debugger_throw_exception_hook(zend_object *exception, zval *file, zval *line, zval *code, char *code_str, zval *message)
342 {
343 zend_class_entry *exception_ce = exception->ce;
344 #else
345 void xdebug_debugger_throw_exception_hook(zval *exception, zval *file, zval *line, zval *code, char *code_str, zval *message)
346 {
347 zend_class_entry *exception_ce = Z_OBJCE_P(exception);
348 #endif
349 xdebug_brk_info *extra_brk_info;
350
351 /* Start JIT if requested and not yet enabled */
352 xdebug_debug_init_if_requested_on_error();
353
354 if (xdebug_is_debug_connection_active() && XG_DBG(breakpoints_allowed)) {
355 int exception_breakpoint_found = 0;
356
357 /* Check if we have a wild card exception breakpoint */
358 if (xdebug_hash_find(XG_DBG(context).exception_breakpoints, "*", 1, (void *) &extra_brk_info)) {
359 exception_breakpoint_found = 1;
360 } else {
361 /* Check if we have a breakpoint on this exception or its parent classes */
362 zend_class_entry *ce_ptr = exception_ce;
363
364 /* Check if we have a breakpoint on this exception or its parent classes */
365 do {
366 if (xdebug_hash_find(XG_DBG(context).exception_breakpoints, (char *) STR_NAME_VAL(ce_ptr->name), STR_NAME_LEN(ce_ptr->name), (void *) &extra_brk_info)) {
367 exception_breakpoint_found = 1;
368 }
369 ce_ptr = ce_ptr->parent;
370 } while (!exception_breakpoint_found && ce_ptr);
371 }
372 #if 0
373 if (XG_DBG(context).resolved_breakpoints && exception_breakpoint_found) {
374 XG_DBG(context).handler->resolve_breakpoints(&(XG_DBG(context)), extra_brk_info);
375 }
376 #endif
377 if (exception_breakpoint_found && xdebug_handle_hit_value(extra_brk_info)) {
378 if (
379 !XG_DBG(context).handler->remote_breakpoint(
380 &(XG_DBG(context)), XG_BASE(stack),
381 Z_STR_P(file), Z_LVAL_P(line), XDEBUG_BREAK,
382 (char*) STR_NAME_VAL(exception_ce->name),
383 code_str ? code_str : ((code && Z_TYPE_P(code) == IS_STRING) ? Z_STRVAL_P(code) : NULL),
384 message ? Z_STRVAL_P(message) : "",
385 extra_brk_info
386 )
387 ) {
388 xdebug_mark_debug_connection_not_active();
389 }
390 }
391 }
392 }
393
394 void xdebug_debugger_error_cb(zend_string *error_filename, int error_lineno, int type, char *error_type_str, char *buffer)
395 {
396 xdebug_brk_info *extra_brk_info = NULL;
397
398 /* Start JIT if requested and not yet enabled */
399 xdebug_debug_init_if_requested_on_error();
400
401 if (xdebug_is_debug_connection_active() && XG_DBG(breakpoints_allowed)) {
402 /* Send notification with warning/notice/error information */
403 if (XG_DBG(context).send_notifications && !XG_DBG(context).inhibit_notifications) {
404 if (!XG_DBG(context).handler->remote_notification(&(XG_DBG(context)), error_filename, error_lineno, type, error_type_str, buffer)) {
405 xdebug_mark_debug_connection_not_active();
406 }
407 }
408
409 /* Check for the pseudo exceptions to allow breakpoints on PHP error statuses */
410 if (
411 xdebug_hash_find(XG_DBG(context).exception_breakpoints, error_type_str, strlen(error_type_str), (void *) &extra_brk_info) ||
412 xdebug_hash_find(XG_DBG(context).exception_breakpoints, "*", 1, (void *) &extra_brk_info)
413 ) {
414 if (xdebug_handle_hit_value(extra_brk_info)) {
415 char *type_str = xdebug_sprintf("%ld", type);
416
417 if (!XG_DBG(context).handler->remote_breakpoint(&(XG_DBG(context)), XG_BASE(stack), error_filename, error_lineno, XDEBUG_BREAK, error_type_str, type_str, buffer, extra_brk_info)) {
418 xdebug_mark_debug_connection_not_active();
419 }
420
421 xdfree(type_str);
422 }
423 }
424 }
425 }
426
427 static int handle_breakpoints(function_stack_entry *fse, int breakpoint_type)
428 {
429 xdebug_brk_info *extra_brk_info = NULL;
430 char *tmp_name = NULL;
431 size_t tmp_len = 0;
432
433 /* Function breakpoints */
434 if (fse->function.type == XFUNC_NORMAL) {
435 if (xdebug_hash_find(XG_DBG(context).function_breakpoints, fse->function.function, strlen(fse->function.function), (void *) &extra_brk_info)) {
436 /* Yup, breakpoint found, we call the handler when it's not
437 * disabled AND handle_hit_value is happy */
438 if (!extra_brk_info->disabled && (extra_brk_info->function_break_type == breakpoint_type)) {
439 if (xdebug_handle_hit_value(extra_brk_info)) {
440 if (fse->user_defined == XDEBUG_BUILT_IN || (breakpoint_type == XDEBUG_BREAKPOINT_TYPE_RETURN)) {
441 if (!XG_DBG(context).handler->remote_breakpoint(&(XG_DBG(context)), XG_BASE(stack), fse->filename, fse->lineno, XDEBUG_BREAK, NULL, 0, NULL, extra_brk_info)) {
442 return 0;
443 }
444 } else {
445 XG_DBG(context).do_break = 1;
446 XG_DBG(context).pending_breakpoint = extra_brk_info;
447 }
448 }
449 }
450 }
451 }
452 /* class->function breakpoints */
453 else if (fse->function.type == XFUNC_MEMBER || fse->function.type == XFUNC_STATIC_MEMBER) {
454 /* Using strlen(ZSTR_VAL(...)) here to cut of the string at the first \0, which is needed
455 * for anonymous classes, in combination with the snprintf() below */
456 tmp_len = strlen(ZSTR_VAL(fse->function.object_class)) + strlen(fse->function.function) + 3;
457 tmp_name = xdmalloc(tmp_len);
458 /* We intentionally do not use xdebug_sprintf because it can create a bottleneck in large
459 * codebases due to setlocale calls. We don't care about the locale here. */
460 snprintf(tmp_name, tmp_len, "%s::%s", ZSTR_VAL(fse->function.object_class), fse->function.function);
461
462 if (xdebug_hash_find(XG_DBG(context).function_breakpoints, tmp_name, tmp_len - 1, (void *) &extra_brk_info)) {
463 /* Yup, breakpoint found, call handler if the breakpoint is not
464 * disabled AND handle_hit_value is happy */
465 if (!extra_brk_info->disabled && (extra_brk_info->function_break_type == breakpoint_type)) {
466 if (xdebug_handle_hit_value(extra_brk_info)) {
467 if (fse->user_defined == XDEBUG_BUILT_IN || (breakpoint_type == XDEBUG_BREAKPOINT_TYPE_RETURN)) {
468 if (!XG_DBG(context).handler->remote_breakpoint(&(XG_DBG(context)), XG_BASE(stack), fse->filename, fse->lineno, XDEBUG_BREAK, NULL, 0, NULL, extra_brk_info)) {
469 return 0;
470 }
471 } else {
472 XG_DBG(context).do_break = 1;
473 XG_DBG(context).pending_breakpoint = extra_brk_info;
474 }
475 }
476 }
477 }
478 xdfree(tmp_name);
479 }
480 return 1;
481 }
482
483 void xdebug_debugger_handle_breakpoints(function_stack_entry *fse, int breakpoint_type)
484 {
485 if (xdebug_is_debug_connection_active() && XG_DBG(breakpoints_allowed)) {
486 if (!handle_breakpoints(fse, breakpoint_type)) {
487 xdebug_mark_debug_connection_not_active();
488 }
489 }
490 }
491
492 static size_t xdebug_ub_write(const char *string, size_t length)
493 {
494 if (xdebug_is_debug_connection_active()) {
495 if (-1 == XG_DBG(context).handler->remote_stream_output(string, length)) {
496 return 0;
497 }
498 }
499 return xdebug_orig_ub_write(string, length);
500 }
501
502 static void xdebug_hook_output_handlers()
503 {
504 /* Override output handler for capturing output */
505 if (xdebug_orig_ub_write == NULL) {
506 xdebug_orig_ub_write = sapi_module.ub_write;
507 sapi_module.ub_write = xdebug_ub_write;
508 }
509 }
510
511 static void xdebug_unhook_output_handlers()
512 {
513 /* Restore original output handler */
514 sapi_module.ub_write = xdebug_orig_ub_write;
515 xdebug_orig_ub_write = NULL;
516 }
517
518 void xdebug_debugger_zend_startup(void)
519 {
520 /* Hook output handlers (header and output writer) */
521 xdebug_hook_output_handlers();
522 }
523
524 void xdebug_debugger_zend_shutdown(void)
525 {
526 /* Remove our hooks to output handlers (header and output writer) */
527 xdebug_unhook_output_handlers();
528 }
529
530 void xdebug_debugger_minit(void)
531 {
532 XG_DBG(breakpoint_count) = 0;
533 }
534
535 void xdebug_debugger_minfo(void)
536 {
537 php_info_print_table_start();
538 php_info_print_table_header(2, "Debugger", "enabled");
539 php_info_print_table_row(2, "IDE Key", XG_DBG(ide_key));
540 php_info_print_table_end();
541 }
542
543 void xdebug_debugger_rinit(void)
544 {
545 char *idekey;
546
547 xdebug_disable_opcache_optimizer();
548
549 /* Get the ide key for this session */
550 XG_DBG(ide_key) = NULL;
551 idekey = xdebug_debugger_get_ide_key();
552 if (idekey && *idekey) {
553 if (XG_DBG(ide_key)) {
554 xdfree(XG_DBG(ide_key));
555 }
556 XG_DBG(ide_key) = xdstrdup(idekey);
557 }
558
559 XG_DBG(no_exec) = 0;
560 xdebug_lib_set_active_symbol_table(NULL);
561
562 /* Check if we have this special get variable that stops a debugging
563 * request without executing any code */
564 {
565 zend_string *stop_no_exec = zend_string_init(ZEND_STRL("XDEBUG_SESSION_STOP_NO_EXEC"), 0);
566 if (
567 (
568 (
569 zend_hash_find(Z_ARR(PG(http_globals)[TRACK_VARS_GET]), stop_no_exec) != NULL
570 ) || (
571 zend_hash_find(Z_ARR(PG(http_globals)[TRACK_VARS_POST]), stop_no_exec) != NULL
572 )
573 )
574 && !SG(headers_sent)
575 ) {
576 xdebug_setcookie("XDEBUG_SESSION", sizeof("XDEBUG_SESSION"), (char*) "", 0, 0, "/", 1, NULL, 0, 0, 1, 0);
577 XG_DBG(no_exec) = 1;
578 }
579 zend_string_release(stop_no_exec);
580 }
581
582 xdebug_mark_debug_connection_not_active();
583
584 XG_DBG(breakpoints_allowed) = 1;
585 XG_DBG(detached) = 0;
586 XG_DBG(breakable_lines_map) = xdebug_hash_alloc(2048, (xdebug_hash_dtor_t) xdebug_line_list_dtor);
587 XG_DBG(function_count) = 0;
588 XG_DBG(class_count) = 0;
589
590 /* Initialize some debugger context properties */
591 XG_DBG(context).program_name = NULL;
592 XG_DBG(context).list.last_filename = NULL;
593 XG_DBG(context).list.last_line = 0;
594 XG_DBG(context).do_break = 0;
595 XG_DBG(context).pending_breakpoint = NULL;
596 XG_DBG(context).do_step = 0;
597 XG_DBG(context).do_next = 0;
598 XG_DBG(context).do_finish = 0;
599 XG_DBG(context).do_connect_to_client = 0;
600
601 /* Statistics and diagnostics */
602 XG_DBG(context).connected_hostname = NULL;
603 XG_DBG(context).connected_port = 0;
604 XG_DBG(context).detached_message = NULL;
605 }
606
607 void xdebug_debugger_post_deactivate(void)
608 {
609 if (XG_DBG(remote_connection_enabled)) {
610 XG_DBG(context).handler->remote_deinit(&(XG_DBG(context)));
611 xdebug_close_socket(XG_DBG(context).socket);
612 }
613 if (XG_DBG(context).program_name) {
614 zend_string_release(XG_DBG(context).program_name);
615 }
616
617 if (XG_DBG(ide_key)) {
618 xdfree(XG_DBG(ide_key));
619 XG_DBG(ide_key) = NULL;
620 }
621
622 if (XG_DBG(context.list.last_filename)) {
623 zend_string_release(XG_DBG(context).list.last_filename);
624 XG_DBG(context).list.last_filename = NULL;
625 }
626
627 xdebug_hash_destroy(XG_DBG(breakable_lines_map));
628 XG_DBG(breakable_lines_map) = NULL;
629
630 if (XG_DBG(context).connected_hostname) {
631 xdfree(XG_DBG(context).connected_hostname);
632 XG_DBG(context).connected_hostname = NULL;
633 }
634
635 if (XG_DBG(context).detached_message) {
636 xdfree(XG_DBG(context).detached_message);
637 XG_DBG(context).detached_message = NULL;
638 }
639 }
640
641 xdebug_set *xdebug_debugger_get_breakable_lines_from_oparray(zend_op_array *opa)
642 {
643 int i;
644 xdebug_set *tmp;
645
646 tmp = xdebug_set_create(opa->line_end);
647
648 for (i = 0; i < opa->last; i++ ) {
649 if (opa->opcodes[i].opcode == ZEND_EXT_STMT ) {
650 xdebug_set_add(tmp, opa->opcodes[i].lineno);
651 }
652 }
653
654 return tmp;
655 }
656
657
658 /* {{{ function/lines map collection helpers */
659 static void xdebug_function_lines_map_dtor(xdebug_function_lines_map_item *lines_map)
660 {
661 xdebug_set_free(lines_map->lines_breakable);
662 xdfree(lines_map);
663 }
664
665 static void xdebug_line_list_dtor(xdebug_lines_list *line_list)
666 {
667 size_t i;
668
669 for (i = 0; i < line_list->count; i++) {
670 xdebug_function_lines_map_dtor(line_list->functions[i]);
671 }
672 xdfree(line_list->functions);
673 xdfree(line_list);
674 }
675
676 static xdebug_lines_list *get_file_function_line_list(zend_string *filename)
677 {
678 xdebug_lines_list *lines_list;
679
680 if (xdebug_hash_find(XG_DBG(breakable_lines_map), ZSTR_VAL(filename), ZSTR_LEN(filename), (void *) &lines_list)) {
681 return lines_list;
682 }
683
684 lines_list = xdmalloc(sizeof(xdebug_lines_list));
685 lines_list->count = 0;
686 lines_list->size = 0;
687 lines_list->functions = NULL;
688
689 xdebug_hash_add(XG_DBG(breakable_lines_map), ZSTR_VAL(filename), ZSTR_LEN(filename), (void *) lines_list);
690
691 return lines_list;
692 }
693
694 static void add_function_to_lines_list(xdebug_lines_list *lines_list, zend_op_array *opa)
695 {
696 xdebug_function_lines_map_item *map_item = xdmalloc(sizeof(xdebug_function_lines_map_item));
697
698 map_item->line_start = opa->line_start;
699 map_item->line_end = opa->line_end;
700 map_item->line_span = opa->line_end - opa->line_start;
701 map_item->lines_breakable = xdebug_debugger_get_breakable_lines_from_oparray(opa);
702
703 if (lines_list->count >= lines_list->size) {
704 lines_list->size = lines_list->size == 0 ? 16 : lines_list->size * 2;
705 lines_list->functions = xdrealloc(lines_list->functions, sizeof(xdebug_function_lines_map_item *) * lines_list->size);
706 }
707 lines_list->functions[lines_list->count] = map_item;
708 lines_list->count++;
709
710 #if PHP_VERSION_ID >= 80100
711 if (opa->num_dynamic_func_defs) {
712 uint32_t i;
713
714 for (i = 0; i < opa->num_dynamic_func_defs; i++) {
715 add_function_to_lines_list(lines_list, opa->dynamic_func_defs[i]);
716 }
717 }
718 #endif
719 }
720 /* }}} */
721
722 static void resolve_breakpoints_for_function(xdebug_lines_list *lines_list, zend_op_array *opa)
723 {
724 add_function_to_lines_list(lines_list, opa);
725 }
726
727 static void resolve_breakpoints_for_class(xdebug_lines_list *file_function_lines_list, zend_class_entry *ce, zend_string *filename)
728 {
729 zend_op_array *function_op_array;
730
731 ZEND_HASH_FOREACH_PTR(&ce->function_table, function_op_array) {
732 if (!ZEND_USER_CODE(function_op_array->type)) {
733 continue;
734 }
735 /* Only resolve if the file names are the same. This is needed in case
736 * of inheritance or traits where op arrays from other files might get introduced */
737 if (ZSTR_LEN(filename) != ZSTR_LEN(function_op_array->filename)) {
738 continue;
739 }
740 if (strcmp(ZSTR_VAL(filename), ZSTR_VAL(function_op_array->filename)) != 0) {
741 continue;
742 }
743 resolve_breakpoints_for_function(file_function_lines_list, function_op_array);
744 } ZEND_HASH_FOREACH_END();
745 }
746
747 void xdebug_debugger_compile_file(zend_op_array *op_array)
748 {
749 zend_op_array *function_op_array;
750 zend_class_entry *class_entry;
751 xdebug_lines_list *file_function_lines_list;
752
753 RETURN_IF_MODE_IS_NOT(XDEBUG_MODE_STEP_DEBUG);
754
755 /* The breakable_lines_map can not be set if another extension compiles
756 * scripts during RINIT */
757 if (!XG_DBG(breakable_lines_map)) {
758 return;
759 }
760
761 file_function_lines_list = get_file_function_line_list(op_array->filename);
762
763 ZEND_HASH_REVERSE_FOREACH_PTR(CG(function_table), function_op_array) {
764 if (_idx == XG_DBG(function_count)) {
765 break;
766 }
767 if (!ZEND_USER_CODE(function_op_array->type)) {
768 continue;
769 }
770 resolve_breakpoints_for_function(file_function_lines_list, function_op_array);
771 } ZEND_HASH_FOREACH_END();
772 XG_DBG(function_count) = CG(function_table)->nNumUsed;
773
774 ZEND_HASH_REVERSE_FOREACH_PTR(CG(class_table), class_entry) {
775 if (_idx == XG_DBG(class_count)) {
776 break;
777 }
778 if (class_entry->type == ZEND_INTERNAL_CLASS) {
779 continue;
780 }
781 resolve_breakpoints_for_class(file_function_lines_list, class_entry, op_array->filename);
782 } ZEND_HASH_FOREACH_END();
783 XG_DBG(class_count) = CG(class_table)->nNumUsed;
784
785 add_function_to_lines_list(file_function_lines_list, op_array);
786
787 if (!xdebug_is_debug_connection_active()) {
788 return;
789 }
790
791 XG_DBG(context).handler->resolve_breakpoints(
792 &(XG_DBG(context)),
793 op_array->filename
794 );
795 }
796
797 static void resolve_breakpoints_for_eval(int eval_id, zend_op_array *opa)
798 {
799 xdebug_lines_list *lines_list;
800 char *eval_filename = xdebug_sprintf("dbgp://%d", eval_id);
801 zend_string *eval_string = zend_string_init(eval_filename, strlen(eval_filename), 0);
802
803 lines_list = get_file_function_line_list(eval_string);
804 add_function_to_lines_list(lines_list, opa);
805
806 resolve_breakpoints_for_function(lines_list, opa);
807
808 if (!xdebug_is_debug_connection_active()) {
809 zend_string_release(eval_string);
810 xdfree(eval_filename);
811 return;
812 }
813
814 XG_DBG(context).handler->resolve_breakpoints(
815 &(XG_DBG(context)),
816 eval_string
817 );
818
819 zend_string_release(eval_string);
820 xdfree(eval_filename);
821 }
822
823 void xdebug_debugger_register_eval(function_stack_entry *fse)
824 {
825 if (xdebug_is_debug_connection_active() && XG_DBG(context).handler->register_eval_id) {
826 int eval_id = XG_DBG(context).handler->register_eval_id(&(XG_DBG(context)), fse);
827
828 resolve_breakpoints_for_eval(eval_id, fse->op_array);
829 }
830 }
831
832 void xdebug_debugger_restart_if_pid_changed()
833 {
834 zend_ulong pid;
835
836 if (!xdebug_is_debug_connection_active()) {
837 return;
838 }
839
840 pid = xdebug_get_pid();
841
842 /* Start debugger if previously a connection was established and this
843 * process no longer has the same PID */
844 if (XG_DBG(remote_connection_pid) != pid) {
845 xdebug_restart_debugger();
846 }
847 }
848
849 PHP_FUNCTION(xdebug_break)
850 {
851 RETURN_FALSE_IF_MODE_IS_NOT(XDEBUG_MODE_STEP_DEBUG);
852
853 xdebug_debug_init_if_requested_on_xdebug_break();
854
855 if (!xdebug_is_debug_connection_active()) {
856 RETURN_FALSE;
857 }
858
859 register_compiled_variables();
860
861 XG_DBG(context).do_break = 1;
862 XG_DBG(context).pending_breakpoint = NULL;
863
864 RETURN_TRUE;
865 }
866
867 PHP_FUNCTION(xdebug_connect_to_client)
868 {
869 RETURN_FALSE_IF_MODE_IS_NOT(XDEBUG_MODE_STEP_DEBUG);
870
871 XG_DBG(context).do_connect_to_client = 1;
872
873 RETURN_TRUE;
874 }
875
876 PHP_FUNCTION(xdebug_is_debugger_active)
877 {
878 RETURN_BOOL(xdebug_is_debug_connection_active());
879 }
880
881 PHP_FUNCTION(xdebug_notify)
882 {
883 function_stack_entry *fse;
884 zval *data;
885
886 RETURN_FALSE_IF_MODE_IS_NOT(XDEBUG_MODE_STEP_DEBUG);
887
888 if (!xdebug_is_debug_connection_active()) {
889 RETURN_FALSE;
890 }
891
892 if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &data) == FAILURE) {
893 return;
894 }
895
896 fse = xdebug_get_stack_frame(0);
897
898 XG_DBG(context).handler->user_notification(
899 &(XG_DBG(context)),
900 fse->filename, fse->lineno,
901 data
902 );
903
904 RETURN_TRUE;
905 }
906