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