xref: /php-src/sapi/phpdbg/phpdbg_bp.c (revision d5c649b3)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Felipe Pena <felipe@php.net>                                |
14    | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
15    | Authors: Bob Weinand <bwoebi@php.net>                                |
16    +----------------------------------------------------------------------+
17 */
18 
19 #include "zend.h"
20 #include "zend_hash.h"
21 #include "phpdbg.h"
22 #include "phpdbg_bp.h"
23 #include "phpdbg_utils.h"
24 #include "zend_globals.h"
25 #include "ext/standard/php_string.h"
26 
27 ZEND_EXTERN_MODULE_GLOBALS(phpdbg)
28 
29 /* {{{ private api functions */
30 static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_file(zend_op_array*);
31 static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_symbol(zend_function*);
32 static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_method(zend_op_array*);
33 static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_opline(phpdbg_opline_ptr_t);
34 static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_opcode(uint8_t);
35 static inline phpdbg_breakbase_t *phpdbg_find_conditional_breakpoint(zend_execute_data *execute_data); /* }}} */
36 
37 /*
38 * Note:
39 *	A break point must always set the correct id and type
40 *	A set breakpoint function must always map new points
41 */
_phpdbg_break_mapping(int id,HashTable * table)42 static inline void _phpdbg_break_mapping(int id, HashTable *table) /* {{{ */
43 {
44 	zend_hash_index_update_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP], id, table);
45 }
46 /* }}} */
47 
48 #define PHPDBG_BREAK_MAPPING(id, table) _phpdbg_break_mapping(id, table)
49 #define PHPDBG_BREAK_UNMAPPING(id) \
50 	zend_hash_index_del(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP], (id))
51 
52 #define PHPDBG_BREAK_INIT(b, t) do {\
53 	memset(&b, 0, sizeof(b)); \
54 	b.id = PHPDBG_G(bp_count)++; \
55 	b.type = t; \
56 	b.disabled = 0;\
57 	b.hits = 0; \
58 } while(0)
59 
phpdbg_file_breaks_dtor(zval * data)60 static void phpdbg_file_breaks_dtor(zval *data) /* {{{ */
61 {
62 	phpdbg_breakfile_t *bp = (phpdbg_breakfile_t*) Z_PTR_P(data);
63 
64 	efree((char*)bp->filename);
65 	efree(bp);
66 } /* }}} */
67 
phpdbg_class_breaks_dtor(zval * data)68 static void phpdbg_class_breaks_dtor(zval *data) /* {{{ */
69 {
70 	phpdbg_breakmethod_t *bp = (phpdbg_breakmethod_t *) Z_PTR_P(data);
71 
72 	efree((char*)bp->class_name);
73 	efree((char*)bp->func_name);
74 	efree(bp);
75 } /* }}} */
76 
phpdbg_opline_class_breaks_dtor(zval * data)77 static void phpdbg_opline_class_breaks_dtor(zval *data) /* {{{ */
78 {
79 	zend_hash_destroy(Z_ARRVAL_P(data));
80 	efree(Z_ARRVAL_P(data));
81 } /* }}} */
82 
phpdbg_opline_breaks_dtor(zval * data)83 static void phpdbg_opline_breaks_dtor(zval *data) /* {{{ */
84 {
85 	phpdbg_breakopline_t *bp = (phpdbg_breakopline_t *) Z_PTR_P(data);
86 
87 	if (bp->class_name) {
88 		efree((char*)bp->class_name);
89 	}
90 	if (bp->func_name) {
91 		efree((char*)bp->func_name);
92 	}
93 	efree(bp);
94 } /* }}} */
95 
phpdbg_reset_breakpoints(void)96 PHPDBG_API void phpdbg_reset_breakpoints(void) /* {{{ */
97 {
98 	HashTable *table;
99 
100 	ZEND_HASH_FOREACH_PTR(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP], table) {
101 		phpdbg_breakbase_t *brake;
102 
103 		ZEND_HASH_FOREACH_PTR(table, brake) {
104 			brake->hits = 0;
105 		} ZEND_HASH_FOREACH_END();
106 	} ZEND_HASH_FOREACH_END();
107 } /* }}} */
108 
phpdbg_export_breakpoints(FILE * handle)109 PHPDBG_API void phpdbg_export_breakpoints(FILE *handle) /* {{{ */
110 {
111 	char *string;
112 	phpdbg_export_breakpoints_to_string(&string);
113 	fputs(string, handle);
114 }
115 /* }}} */
116 
phpdbg_export_breakpoints_to_string(char ** str)117 PHPDBG_API void phpdbg_export_breakpoints_to_string(char **str) /* {{{ */
118 {
119 	HashTable *table;
120 	zend_ulong id = 0L;
121 
122 	*str = "";
123 
124 	if (zend_hash_num_elements(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP])) {
125 		phpdbg_notice("Exporting %d breakpoints", zend_hash_num_elements(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP]));
126 
127 		/* this only looks like magic, it isn't */
128 		ZEND_HASH_FOREACH_NUM_KEY_PTR(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP], id, table) {
129 			phpdbg_breakbase_t *brake;
130 
131 			ZEND_HASH_FOREACH_PTR(table, brake) {
132 				if (brake->id == id) {
133 					char *new_str = NULL;
134 
135 					switch (brake->type) {
136 						case PHPDBG_BREAK_FILE: {
137 							zend_string *filename = php_addcslashes_str(((phpdbg_breakfile_t*)brake)->filename, strlen(((phpdbg_breakfile_t*)brake)->filename), "\\\"\n", 3);
138 							phpdbg_asprintf(&new_str,
139 								"%sbreak \"%s\":"ZEND_ULONG_FMT"\n", *str,
140 								ZSTR_VAL(filename),
141 								((phpdbg_breakfile_t*)brake)->line);
142 							zend_string_release(filename);
143 						} break;
144 
145 						case PHPDBG_BREAK_SYM: {
146 							phpdbg_asprintf(&new_str,
147 								"%sbreak %s\n", *str,
148 								((phpdbg_breaksymbol_t*)brake)->symbol);
149 						} break;
150 
151 						case PHPDBG_BREAK_METHOD: {
152 							phpdbg_asprintf(&new_str,
153 								"%sbreak %s::%s\n", *str,
154 								((phpdbg_breakmethod_t*)brake)->class_name,
155 								((phpdbg_breakmethod_t*)brake)->func_name);
156 						} break;
157 
158 						case PHPDBG_BREAK_METHOD_OPLINE: {
159 							phpdbg_asprintf(&new_str,
160 								"%sbreak %s::%s#"ZEND_ULONG_FMT"\n", *str,
161 								((phpdbg_breakopline_t*)brake)->class_name,
162 								((phpdbg_breakopline_t*)brake)->func_name,
163 								((phpdbg_breakopline_t*)brake)->opline_num);
164 						} break;
165 
166 						case PHPDBG_BREAK_FUNCTION_OPLINE: {
167 							phpdbg_asprintf(&new_str,
168 								"%sbreak %s#"ZEND_ULONG_FMT"\n", *str,
169 								((phpdbg_breakopline_t*)brake)->func_name,
170 								((phpdbg_breakopline_t*)brake)->opline_num);
171 						} break;
172 
173 						case PHPDBG_BREAK_FILE_OPLINE: {
174 							zend_string *filename = php_addcslashes_str(((phpdbg_breakopline_t*)brake)->class_name, strlen(((phpdbg_breakopline_t*)brake)->class_name), "\\\"\n", 3);
175 							phpdbg_asprintf(&new_str,
176 								"%sbreak \"%s\":#"ZEND_ULONG_FMT"\n", *str,
177 								ZSTR_VAL(filename),
178 								((phpdbg_breakopline_t*)brake)->opline_num);
179 							zend_string_release(filename);
180 						} break;
181 
182 						case PHPDBG_BREAK_OPCODE: {
183 							phpdbg_asprintf(&new_str,
184 								"%sbreak %s\n", *str,
185 								((phpdbg_breakop_t*)brake)->name);
186 						} break;
187 
188 						case PHPDBG_BREAK_COND: {
189 							phpdbg_breakcond_t *conditional = (phpdbg_breakcond_t*) brake;
190 
191 							if (conditional->paramed) {
192 								switch (conditional->param.type) {
193 		                            case NUMERIC_FUNCTION_PARAM:
194 		                                phpdbg_asprintf(&new_str,
195 		                                    "%sbreak at %s#"ZEND_ULONG_FMT" if %s\n",
196 		                                    *str, conditional->param.str, conditional->param.num, conditional->code);
197 		                            break;
198 
199 		                            case NUMERIC_METHOD_PARAM:
200 		                                phpdbg_asprintf(&new_str,
201 		                                    "%sbreak at %s::%s#"ZEND_ULONG_FMT" if %s\n",
202 		                                    *str, conditional->param.method.class, conditional->param.method.name, conditional->param.num, conditional->code);
203 		                            break;
204 
205 		                            case ADDR_PARAM:
206 		                                phpdbg_asprintf(&new_str,
207 		                                    "%sbreak at 0X"ZEND_ULONG_FMT" if %s\n",
208 		                                    *str, conditional->param.addr, conditional->code);
209 		                            break;
210 
211 									case STR_PARAM:
212 										phpdbg_asprintf(&new_str,
213 											"%sbreak at %s if %s\n", *str, conditional->param.str, conditional->code);
214 									break;
215 
216 									case METHOD_PARAM:
217 										phpdbg_asprintf(&new_str,
218 											"%sbreak at %s::%s if %s\n", *str,
219 											conditional->param.method.class, conditional->param.method.name,
220 											conditional->code);
221 									break;
222 
223 									case FILE_PARAM: {
224 										zend_string *filename = php_addcslashes_str(conditional->param.file.name, strlen(conditional->param.file.name), "\\\"\n", 3);
225 										phpdbg_asprintf(&new_str,
226 											"%sbreak at \"%s\":"ZEND_ULONG_FMT" if %s\n", *str,
227 											ZSTR_VAL(filename), conditional->param.file.line,
228 											conditional->code);
229 										zend_string_release(filename);
230 									} break;
231 
232 									default: { /* do nothing */ } break;
233 								}
234 							} else {
235 								phpdbg_asprintf(&new_str, "%sbreak if %s\n", str, conditional->code);
236 							}
237 						} break;
238 
239 						default: continue;
240 					}
241 
242 					if ((*str)[0]) {
243 						free(*str);
244 					}
245 					*str = new_str;
246 				}
247 			} ZEND_HASH_FOREACH_END();
248 		} ZEND_HASH_FOREACH_END();
249 	}
250 
251 	if ((*str) && !(*str)[0]) {
252 		*str = NULL;
253 	}
254 } /* }}} */
255 
phpdbg_set_breakpoint_file(const char * path,size_t path_len,zend_ulong line_num)256 PHPDBG_API void phpdbg_set_breakpoint_file(const char *path, size_t path_len, zend_ulong line_num) /* {{{ */
257 {
258 	php_stream_statbuf ssb;
259 	char realpath[MAXPATHLEN];
260 	const char *original_path = path;
261 	bool pending = 0;
262 	zend_string *path_str;
263 
264 	HashTable *broken, *file_breaks = &PHPDBG_G(bp)[PHPDBG_BREAK_FILE];
265 	phpdbg_breakfile_t new_break;
266 
267 	if (!path_len) {
268 		if (VCWD_REALPATH(path, realpath)) {
269 			path = realpath;
270 		}
271 	}
272 	path_len = strlen(path);
273 
274 	phpdbg_debug("file path: %s, resolved path: %s, was compiled: %d\n", original_path, path, zend_hash_str_exists(&PHPDBG_G(file_sources), path, path_len));
275 
276 	if (!zend_hash_str_exists(&PHPDBG_G(file_sources), path, path_len)) {
277 		if (php_stream_stat_path(path, &ssb) == FAILURE) {
278 			if (original_path[0] == '/') {
279 				phpdbg_error("Cannot stat %s, it does not exist", original_path);
280 				return;
281 			}
282 
283 			file_breaks = &PHPDBG_G(bp)[PHPDBG_BREAK_FILE_PENDING];
284 			path = original_path;
285 			path_len = strlen(path);
286 			pending = 1;
287 		} else if (!(ssb.sb.st_mode & (S_IFREG|S_IFLNK))) {
288 			phpdbg_error("Cannot set breakpoint in %s, it is not a regular file", path);
289 			return;
290 		} else {
291 			phpdbg_debug("File exists, but not compiled\n");
292 		}
293 	}
294 
295 	path_str = zend_string_init(path, path_len, 0);
296 
297 	if (!(broken = zend_hash_find_ptr(file_breaks, path_str))) {
298 		HashTable breaks;
299 		zend_hash_init(&breaks, 8, NULL, phpdbg_file_breaks_dtor, 0);
300 
301 		broken = zend_hash_add_mem(file_breaks, path_str, &breaks, sizeof(HashTable));
302 	}
303 
304 	if (!zend_hash_index_exists(broken, line_num)) {
305 		PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_FILE);
306 		new_break.filename = estrndup(path, path_len);
307 		new_break.line = line_num;
308 
309 		zend_hash_index_update_mem(broken, line_num, &new_break, sizeof(phpdbg_breakfile_t));
310 
311 		PHPDBG_BREAK_MAPPING(new_break.id, broken);
312 
313 		if (pending) {
314 			zend_string *file;
315 			ZEND_HASH_MAP_FOREACH_STR_KEY(&PHPDBG_G(file_sources), file) {
316 				HashTable *fileht;
317 
318 				phpdbg_debug("Compare against loaded %s\n", ZSTR_VAL(file));
319 
320 				if (!(pending = ((fileht = phpdbg_resolve_pending_file_break_ex(ZSTR_VAL(file), ZSTR_LEN(file), path_str, broken)) == NULL))) {
321 					new_break = *(phpdbg_breakfile_t *) zend_hash_index_find_ptr(fileht, line_num);
322 					break;
323 				}
324 			} ZEND_HASH_FOREACH_END();
325 		}
326 
327 		if (pending) {
328 			PHPDBG_G(flags) |= PHPDBG_HAS_PENDING_FILE_BP;
329 
330 			phpdbg_notice("Pending breakpoint #%d added at %s:"ZEND_ULONG_FMT"", new_break.id, new_break.filename, new_break.line);
331 		} else {
332 			PHPDBG_G(flags) |= PHPDBG_HAS_FILE_BP;
333 
334 			phpdbg_notice("Breakpoint #%d added at %s:"ZEND_ULONG_FMT"", new_break.id, new_break.filename, new_break.line);
335 		}
336 	} else {
337 		phpdbg_error("Breakpoint at %s:"ZEND_ULONG_FMT" exists", path, line_num);
338 	}
339 
340 	zend_string_release(path_str);
341 } /* }}} */
342 
phpdbg_resolve_pending_file_break_ex(const char * file,uint32_t filelen,zend_string * cur,HashTable * fileht)343 PHPDBG_API HashTable *phpdbg_resolve_pending_file_break_ex(const char *file, uint32_t filelen, zend_string *cur, HashTable *fileht) /* {{{ */
344 {
345 	phpdbg_debug("file: %s, filelen: %u, cur: %s, curlen %u, pos: %c, memcmp: %d\n", file, filelen, ZSTR_VAL(cur), ZSTR_LEN(cur), filelen > ZSTR_LEN(cur) ? file[filelen - ZSTR_LEN(cur) - 1] : '?', filelen > ZSTR_LEN(cur) ? memcmp(file + filelen - ZSTR_LEN(cur), ZSTR_VAL(cur), ZSTR_LEN(cur)) : 0);
346 
347 #ifdef _WIN32
348 # define WIN32_PATH_CHECK file[filelen - ZSTR_LEN(cur) - 1] == '\\'
349 #else
350 # define WIN32_PATH_CHECK 0
351 #endif
352 
353 	if (((ZSTR_LEN(cur) < filelen && (file[filelen - ZSTR_LEN(cur) - 1] == '/' || WIN32_PATH_CHECK)) || filelen == ZSTR_LEN(cur)) && !memcmp(file + filelen - ZSTR_LEN(cur), ZSTR_VAL(cur), ZSTR_LEN(cur))) {
354 		phpdbg_breakfile_t *brake, new_brake;
355 		HashTable *master;
356 
357 		PHPDBG_G(flags) |= PHPDBG_HAS_FILE_BP;
358 
359 		if (!(master = zend_hash_str_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE], file, filelen))) {
360 			HashTable new_ht;
361 			zend_hash_init(&new_ht, 8, NULL, phpdbg_file_breaks_dtor, 0);
362 			master = zend_hash_str_add_mem(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE], file, filelen, &new_ht, sizeof(HashTable));
363 		}
364 
365 		ZEND_HASH_FOREACH_PTR(fileht, brake) {
366 			new_brake = *brake;
367 			new_brake.filename = estrndup(file, filelen);
368 			PHPDBG_BREAK_UNMAPPING(brake->id);
369 
370 			if (zend_hash_index_add_mem(master, brake->line, &new_brake, sizeof(phpdbg_breakfile_t))) {
371 				PHPDBG_BREAK_MAPPING(brake->id, master);
372 			}
373 		} ZEND_HASH_FOREACH_END();
374 
375 		zend_hash_del(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_PENDING], cur);
376 
377 		if (!zend_hash_num_elements(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_PENDING])) {
378 			PHPDBG_G(flags) &= ~PHPDBG_HAS_PENDING_FILE_BP;
379 		}
380 
381 		phpdbg_debug("compiled file: %s, cur bp file: %s\n", file, ZSTR_VAL(cur));
382 
383 		return master;
384 	}
385 
386 	return NULL;
387 } /* }}} */
388 
phpdbg_resolve_pending_file_break(const char * file)389 PHPDBG_API void phpdbg_resolve_pending_file_break(const char *file) /* {{{ */
390 {
391 	HashTable *fileht;
392 	uint32_t filelen = strlen(file);
393 	zend_string *cur;
394 
395 	phpdbg_debug("was compiled: %s\n", file);
396 
397 	ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_PENDING], cur, fileht) {
398 		phpdbg_debug("check bp: %s\n", ZSTR_VAL(cur));
399 
400 		phpdbg_resolve_pending_file_break_ex(file, filelen, cur, fileht);
401 	} ZEND_HASH_FOREACH_END();
402 } /* }}} */
403 
phpdbg_set_breakpoint_symbol(const char * name,size_t name_len)404 PHPDBG_API void phpdbg_set_breakpoint_symbol(const char *name, size_t name_len) /* {{{ */
405 {
406 	char *lcname;
407 
408 	if (*name == '\\') {
409 		name++;
410 		name_len--;
411 	}
412 
413 	lcname = zend_str_tolower_dup(name, name_len);
414 
415 	if (!zend_hash_str_exists(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], name, name_len)) {
416 		phpdbg_breaksymbol_t new_break;
417 
418 		PHPDBG_G(flags) |= PHPDBG_HAS_SYM_BP;
419 
420 		PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_SYM);
421 		new_break.symbol = estrndup(name, name_len);
422 
423 		zend_hash_str_update_mem(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], lcname, name_len, &new_break, sizeof(phpdbg_breaksymbol_t));
424 
425 		phpdbg_notice("Breakpoint #%d added at %s", new_break.id, new_break.symbol);
426 
427 		PHPDBG_BREAK_MAPPING(new_break.id, &PHPDBG_G(bp)[PHPDBG_BREAK_SYM]);
428 	} else {
429 		phpdbg_error("Breakpoint exists at %s", name);
430 	}
431 
432 	efree(lcname);
433 } /* }}} */
434 
phpdbg_set_breakpoint_method(const char * class_name,const char * func_name)435 PHPDBG_API void phpdbg_set_breakpoint_method(const char *class_name, const char *func_name) /* {{{ */
436 {
437 	HashTable class_breaks, *class_table;
438 	size_t class_len = strlen(class_name);
439 	size_t func_len = strlen(func_name);
440 	char *func_lcname, *class_lcname;
441 
442 	if (*class_name == '\\') {
443 		class_name++;
444 		class_len--;
445 	}
446 
447 	func_lcname = zend_str_tolower_dup(func_name, func_len);
448 	class_lcname = zend_str_tolower_dup(class_name, class_len);
449 
450 	if (!(class_table = zend_hash_str_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD], class_lcname, class_len))) {
451 		zend_hash_init(&class_breaks, 8, NULL, phpdbg_class_breaks_dtor, 0);
452 		class_table = zend_hash_str_update_mem(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD], class_lcname, class_len, &class_breaks, sizeof(HashTable));
453 	}
454 
455 	if (!zend_hash_str_exists(class_table, func_lcname, func_len)) {
456 		phpdbg_breakmethod_t new_break;
457 
458 		PHPDBG_G(flags) |= PHPDBG_HAS_METHOD_BP;
459 
460 		PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_METHOD);
461 		new_break.class_name = estrndup(class_name, class_len);
462 		new_break.class_len = class_len;
463 		new_break.func_name = estrndup(func_name, func_len);
464 		new_break.func_len = func_len;
465 
466 		zend_hash_str_update_mem(class_table, func_lcname, func_len, &new_break, sizeof(phpdbg_breakmethod_t));
467 
468 		phpdbg_notice("Breakpoint #%d added at %s::%s", new_break.id, class_name, func_name);
469 
470 		PHPDBG_BREAK_MAPPING(new_break.id, class_table);
471 	} else {
472 		phpdbg_error("Breakpoint exists at %s::%s", class_name, func_name);
473 	}
474 
475 	efree(func_lcname);
476 	efree(class_lcname);
477 } /* }}} */
478 
phpdbg_set_breakpoint_opline(zend_ulong opline)479 PHPDBG_API void phpdbg_set_breakpoint_opline(zend_ulong opline) /* {{{ */
480 {
481 	if (!zend_hash_index_exists(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], opline)) {
482 		phpdbg_breakline_t new_break;
483 
484 		PHPDBG_G(flags) |= PHPDBG_HAS_OPLINE_BP;
485 
486 		PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_OPLINE);
487 		new_break.name = NULL;
488 		new_break.opline = opline;
489 		new_break.base = NULL;
490 
491 		zend_hash_index_update_mem(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], opline, &new_break, sizeof(phpdbg_breakline_t));
492 
493 		phpdbg_notice("Breakpoint #%d added at #"ZEND_ULONG_FMT, new_break.id, new_break.opline);
494 		PHPDBG_BREAK_MAPPING(new_break.id, &PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE]);
495 	} else {
496 		phpdbg_error("Breakpoint exists at #"ZEND_ULONG_FMT, opline);
497 	}
498 } /* }}} */
499 
phpdbg_resolve_op_array_break(phpdbg_breakopline_t * brake,zend_op_array * op_array)500 PHPDBG_API int phpdbg_resolve_op_array_break(phpdbg_breakopline_t *brake, zend_op_array *op_array) /* {{{ */
501 {
502 	phpdbg_breakline_t opline_break;
503 	if (op_array->last <= brake->opline_num) {
504 		if (brake->class_name == NULL) {
505 			phpdbg_error("There are only %d oplines in function %s (breaking at opline "ZEND_ULONG_FMT" impossible)", op_array->last, brake->func_name, brake->opline_num);
506 		} else if (brake->func_name == NULL) {
507 			phpdbg_error("There are only %d oplines in file %s (breaking at opline "ZEND_ULONG_FMT" impossible)", op_array->last, brake->class_name, brake->opline_num);
508 		} else {
509 			phpdbg_error("There are only %d oplines in method %s::%s (breaking at opline "ZEND_ULONG_FMT" impossible)", op_array->last, brake->class_name, brake->func_name, brake->opline_num);
510 		}
511 
512 		return FAILURE;
513 	}
514 
515 	opline_break.disabled = 0;
516 	opline_break.hits = 0;
517 	opline_break.id = brake->id;
518 	opline_break.opline = brake->opline = (zend_ulong)(op_array->opcodes + brake->opline_num);
519 	opline_break.name = NULL;
520 	opline_break.base = brake;
521 	if (op_array->scope) {
522 		opline_break.type = PHPDBG_BREAK_METHOD_OPLINE;
523 	} else if (op_array->function_name) {
524 		opline_break.type = PHPDBG_BREAK_FUNCTION_OPLINE;
525 	} else {
526 		opline_break.type = PHPDBG_BREAK_FILE_OPLINE;
527 	}
528 
529 	PHPDBG_G(flags) |= PHPDBG_HAS_OPLINE_BP;
530 
531 	zend_hash_index_update_mem(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], opline_break.opline, &opline_break, sizeof(phpdbg_breakline_t));
532 
533 	return SUCCESS;
534 } /* }}} */
535 
phpdbg_resolve_op_array_breaks(zend_op_array * op_array)536 PHPDBG_API void phpdbg_resolve_op_array_breaks(zend_op_array *op_array) /* {{{ */
537 {
538 	HashTable *func_table = &PHPDBG_G(bp)[PHPDBG_BREAK_FUNCTION_OPLINE];
539 	HashTable *oplines_table;
540 	phpdbg_breakopline_t *brake;
541 
542 	if (op_array->scope != NULL && !(func_table = zend_hash_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD_OPLINE], op_array->scope->name))) {
543 		return;
544 	}
545 
546 	if (op_array->function_name == NULL) {
547 		if (!(oplines_table = zend_hash_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE], op_array->filename))) {
548 			return;
549 		}
550 	} else if (!op_array->function_name || !(oplines_table = zend_hash_find_ptr(func_table, op_array->function_name))) {
551 		return;
552 	}
553 
554 	ZEND_HASH_MAP_FOREACH_PTR(oplines_table, brake) {
555 		if (phpdbg_resolve_op_array_break(brake, op_array) == SUCCESS) {
556 			phpdbg_breakline_t *opline_break;
557 
558 			zend_hash_internal_pointer_end(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE]);
559 			opline_break = zend_hash_get_current_data_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE]);
560 
561 			phpdbg_notice("Breakpoint #%d resolved at %s%s%s#"ZEND_ULONG_FMT" (opline #"ZEND_ULONG_FMT")",
562 				opline_break->id,
563 				brake->class_name ? brake->class_name : "",
564 				brake->class_name && brake->func_name ? "::" : "",
565 				brake->func_name ? brake->func_name : "",
566 				brake->opline_num,
567 				opline_break->opline);
568 		}
569 	} ZEND_HASH_FOREACH_END();
570 } /* }}} */
571 
phpdbg_resolve_opline_break(phpdbg_breakopline_t * new_break)572 PHPDBG_API int phpdbg_resolve_opline_break(phpdbg_breakopline_t *new_break) /* {{{ */
573 {
574 	HashTable *func_table = EG(function_table);
575 	zend_function *func;
576 
577 	if (new_break->func_name == NULL) {
578 		if (EG(current_execute_data) == NULL) {
579 			if (PHPDBG_G(ops) != NULL && !memcmp(PHPDBG_G(ops)->filename, new_break->class_name, new_break->class_len)) {
580 				if (phpdbg_resolve_op_array_break(new_break, PHPDBG_G(ops)) == SUCCESS) {
581 					return SUCCESS;
582 				} else {
583 					return 2;
584 				}
585 			}
586 			return FAILURE;
587 		} else {
588 			zend_execute_data *execute_data = EG(current_execute_data);
589 			do {
590 				if (ZEND_USER_CODE(execute_data->func->common.type)) {
591 					zend_op_array *op_array = &execute_data->func->op_array;
592 					if (op_array->function_name == NULL && op_array->scope == NULL && new_break->class_len == ZSTR_LEN(op_array->filename) && !memcmp(ZSTR_VAL(op_array->filename), new_break->class_name, new_break->class_len)) {
593 						if (phpdbg_resolve_op_array_break(new_break, op_array) == SUCCESS) {
594 							return SUCCESS;
595 						} else {
596 							return 2;
597 						}
598 					}
599 				}
600 			} while ((execute_data = execute_data->prev_execute_data) != NULL);
601 			return FAILURE;
602 		}
603 	}
604 
605 	if (new_break->class_name != NULL) {
606 		zend_class_entry *ce;
607 		if (!(ce = zend_hash_str_find_ptr(EG(class_table), zend_str_tolower_dup(new_break->class_name, new_break->class_len), new_break->class_len))) {
608 			return FAILURE;
609 		}
610 		func_table = &ce->function_table;
611 	}
612 
613 	if (!(func = zend_hash_str_find_ptr(func_table, zend_str_tolower_dup(new_break->func_name, new_break->func_len), new_break->func_len))) {
614 		if (new_break->class_name != NULL && new_break->func_name != NULL) {
615 			phpdbg_error("Method %s doesn't exist in class %s", new_break->func_name, new_break->class_name);
616 			return 2;
617 		}
618 		return FAILURE;
619 	}
620 
621 	if (func->type != ZEND_USER_FUNCTION) {
622 		if (new_break->class_name == NULL) {
623 			phpdbg_error("%s is not a user defined function, no oplines exist", new_break->func_name);
624 		} else {
625 			phpdbg_error("%s::%s is not a user defined method, no oplines exist", new_break->class_name, new_break->func_name);
626 		}
627 		return 2;
628 	}
629 
630 	if (phpdbg_resolve_op_array_break(new_break, &func->op_array) == FAILURE) {
631 		return 2;
632 	}
633 
634 	return SUCCESS;
635 } /* }}} */
636 
637 /* TODO ... method/function oplines need to be normalized (leading backslash, lowercase) and file oplines need to be resolved properly */
638 
phpdbg_set_breakpoint_method_opline(const char * class,const char * method,zend_ulong opline)639 PHPDBG_API void phpdbg_set_breakpoint_method_opline(const char *class, const char *method, zend_ulong opline) /* {{{ */
640 {
641 	phpdbg_breakopline_t new_break;
642 	HashTable class_breaks, *class_table;
643 	HashTable method_breaks, *method_table;
644 
645 	PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_METHOD_OPLINE);
646 	new_break.func_len = strlen(method);
647 	new_break.func_name = estrndup(method, new_break.func_len);
648 	new_break.class_len = strlen(class);
649 	new_break.class_name = estrndup(class, new_break.class_len);
650 	new_break.opline_num = opline;
651 	new_break.opline = 0;
652 
653 	switch (phpdbg_resolve_opline_break(&new_break)) {
654 		case FAILURE:
655 			phpdbg_notice("Pending breakpoint #%d at %s::%s#"ZEND_ULONG_FMT, new_break.id, new_break.class_name, new_break.func_name, opline);
656 			break;
657 
658 		case SUCCESS:
659 			phpdbg_notice("Breakpoint #%d added at %s::%s#"ZEND_ULONG_FMT, new_break.id, new_break.class_name, new_break.func_name, opline);
660 			break;
661 
662 		case 2:
663 			return;
664 	}
665 
666 	if (!(class_table = zend_hash_str_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD_OPLINE], new_break.class_name, new_break.class_len))) {
667 		zend_hash_init(&class_breaks, 8, NULL, phpdbg_opline_class_breaks_dtor, 0);
668 		class_table = zend_hash_str_update_mem(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD_OPLINE], new_break.class_name, new_break.class_len, &class_breaks, sizeof(HashTable));
669 	}
670 
671 	if (!(method_table = zend_hash_str_find_ptr(class_table, new_break.func_name, new_break.func_len))) {
672 		zend_hash_init(&method_breaks, 8, NULL, phpdbg_opline_breaks_dtor, 0);
673 		method_table = zend_hash_str_update_mem(class_table, new_break.func_name, new_break.func_len, &method_breaks, sizeof(HashTable));
674 	}
675 
676 	if (zend_hash_index_exists(method_table, opline)) {
677 		phpdbg_error("Breakpoint already exists for %s::%s#"ZEND_ULONG_FMT, new_break.class_name, new_break.func_name, opline);
678 		efree((char*)new_break.func_name);
679 		efree((char*)new_break.class_name);
680 		PHPDBG_G(bp_count)--;
681 		return;
682 	}
683 
684 	PHPDBG_G(flags) |= PHPDBG_HAS_METHOD_OPLINE_BP;
685 
686 	PHPDBG_BREAK_MAPPING(new_break.id, method_table);
687 
688 	zend_hash_index_update_mem(method_table, opline, &new_break, sizeof(phpdbg_breakopline_t));
689 }
690 /* }}} */
691 
phpdbg_set_breakpoint_function_opline(const char * function,zend_ulong opline)692 PHPDBG_API void phpdbg_set_breakpoint_function_opline(const char *function, zend_ulong opline) /* {{{ */
693 {
694 	phpdbg_breakopline_t new_break;
695 	HashTable func_breaks, *func_table;
696 
697 	PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_FUNCTION_OPLINE);
698 	new_break.func_len = strlen(function);
699 	new_break.func_name = estrndup(function, new_break.func_len);
700 	new_break.class_len = 0;
701 	new_break.class_name = NULL;
702 	new_break.opline_num = opline;
703 	new_break.opline = 0;
704 
705 	switch (phpdbg_resolve_opline_break(&new_break)) {
706 		case FAILURE:
707 			phpdbg_notice("Pending breakpoint #%d at %s#"ZEND_ULONG_FMT, new_break.id, new_break.func_name, opline);
708 			break;
709 
710 		case SUCCESS:
711 			phpdbg_notice("Breakpoint #%d added at %s#"ZEND_ULONG_FMT, new_break.id, new_break.func_name, opline);
712 			break;
713 
714 		case 2:
715 			return;
716 	}
717 
718 	if (!(func_table = zend_hash_str_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_FUNCTION_OPLINE], new_break.func_name, new_break.func_len))) {
719 		zend_hash_init(&func_breaks, 8, NULL, phpdbg_opline_breaks_dtor, 0);
720 		func_table = zend_hash_str_update_mem(&PHPDBG_G(bp)[PHPDBG_BREAK_FUNCTION_OPLINE], new_break.func_name, new_break.func_len, &func_breaks, sizeof(HashTable));
721 	}
722 
723 	if (zend_hash_index_exists(func_table, opline)) {
724 		phpdbg_error("Breakpoint already exists for %s#"ZEND_ULONG_FMT, new_break.func_name, opline);
725 		efree((char*)new_break.func_name);
726 		PHPDBG_G(bp_count)--;
727 		return;
728 	}
729 
730 	PHPDBG_BREAK_MAPPING(new_break.id, func_table);
731 
732 	PHPDBG_G(flags) |= PHPDBG_HAS_FUNCTION_OPLINE_BP;
733 
734 	zend_hash_index_update_mem(func_table, opline, &new_break, sizeof(phpdbg_breakopline_t));
735 }
736 /* }}} */
737 
phpdbg_set_breakpoint_file_opline(const char * file,zend_ulong opline)738 PHPDBG_API void phpdbg_set_breakpoint_file_opline(const char *file, zend_ulong opline) /* {{{ */
739 {
740 	phpdbg_breakopline_t new_break;
741 	HashTable file_breaks, *file_table;
742 
743 	PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_FILE_OPLINE);
744 	new_break.func_len = 0;
745 	new_break.func_name = NULL;
746 	new_break.class_len = strlen(file);
747 	new_break.class_name = estrndup(file, new_break.class_len);
748 	new_break.opline_num = opline;
749 	new_break.opline = 0;
750 
751 	switch (phpdbg_resolve_opline_break(&new_break)) {
752 		case FAILURE:
753 			phpdbg_notice("Pending breakpoint #%d at %s:"ZEND_ULONG_FMT, new_break.id, new_break.class_name, opline);
754 			break;
755 
756 		case SUCCESS:
757 			phpdbg_notice("Breakpoint #%d added at %s:"ZEND_ULONG_FMT, new_break.id, new_break.class_name, opline);
758 			break;
759 
760 		case 2:
761 			return;
762 	}
763 
764 	if (!(file_table = zend_hash_str_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE], new_break.class_name, new_break.class_len))) {
765 		zend_hash_init(&file_breaks, 8, NULL, phpdbg_opline_breaks_dtor, 0);
766 		file_table = zend_hash_str_update_mem(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE], new_break.class_name, new_break.class_len, &file_breaks, sizeof(HashTable));
767 	}
768 
769 	if (zend_hash_index_exists(file_table, opline)) {
770 		phpdbg_error("Breakpoint already exists for %s:"ZEND_ULONG_FMT, new_break.class_name, opline);
771 		efree((char*)new_break.class_name);
772 		PHPDBG_G(bp_count)--;
773 		return;
774 	}
775 
776 	PHPDBG_BREAK_MAPPING(new_break.id, file_table);
777 
778 	PHPDBG_G(flags) |= PHPDBG_HAS_FILE_OPLINE_BP;
779 
780 	zend_hash_index_update_mem(file_table, opline, &new_break, sizeof(phpdbg_breakopline_t));
781 }
782 /* }}} */
783 
phpdbg_set_breakpoint_opcode(const char * name,size_t name_len)784 PHPDBG_API void phpdbg_set_breakpoint_opcode(const char *name, size_t name_len) /* {{{ */
785 {
786 	phpdbg_breakop_t new_break;
787 	zend_ulong hash = zend_hash_func(name, name_len);
788 
789 	if (zend_hash_index_exists(&PHPDBG_G(bp)[PHPDBG_BREAK_OPCODE], hash)) {
790 		phpdbg_error("Breakpoint exists for %s", name);
791 		return;
792 	}
793 
794 	PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_OPCODE);
795 	new_break.hash = hash;
796 	new_break.name = estrndup(name, name_len);
797 
798 	zend_hash_index_update_mem(&PHPDBG_G(bp)[PHPDBG_BREAK_OPCODE], hash, &new_break, sizeof(phpdbg_breakop_t));
799 
800 	PHPDBG_G(flags) |= PHPDBG_HAS_OPCODE_BP;
801 
802 	phpdbg_notice("Breakpoint #%d added at %s", new_break.id, name);
803 	PHPDBG_BREAK_MAPPING(new_break.id, &PHPDBG_G(bp)[PHPDBG_BREAK_OPCODE]);
804 } /* }}} */
805 
phpdbg_set_breakpoint_opline_ex(phpdbg_opline_ptr_t opline)806 PHPDBG_API void phpdbg_set_breakpoint_opline_ex(phpdbg_opline_ptr_t opline) /* {{{ */
807 {
808 	if (!zend_hash_index_exists(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], (zend_ulong) opline)) {
809 		phpdbg_breakline_t new_break;
810 
811 		PHPDBG_G(flags) |= PHPDBG_HAS_OPLINE_BP;
812 
813 		PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_OPLINE);
814 		new_break.opline = (zend_ulong) opline;
815 		new_break.base = NULL;
816 
817 		zend_hash_index_update_mem(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], (zend_ulong) opline, &new_break, sizeof(phpdbg_breakline_t));
818 
819 		phpdbg_notice("Breakpoint #%d added at #"ZEND_ULONG_FMT, new_break.id, new_break.opline);
820 		PHPDBG_BREAK_MAPPING(new_break.id, &PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE]);
821 	} else {
822 		phpdbg_error("Breakpoint exists for opline #"ZEND_ULONG_FMT, (zend_ulong) opline);
823 	}
824 } /* }}} */
825 
phpdbg_create_conditional_break(phpdbg_breakcond_t * brake,const phpdbg_param_t * param,const char * expr,size_t expr_len,zend_ulong hash)826 static inline void phpdbg_create_conditional_break(phpdbg_breakcond_t *brake, const phpdbg_param_t *param, const char *expr, size_t expr_len, zend_ulong hash) /* {{{ */
827 {
828 	phpdbg_breakcond_t new_break;
829 	uint32_t cops = CG(compiler_options);
830 	zend_string *bp_code;
831 
832 	if (param) {
833 		switch (param->type) {
834 			case STR_PARAM:
835 			case NUMERIC_FUNCTION_PARAM:
836 			case METHOD_PARAM:
837 			case NUMERIC_METHOD_PARAM:
838 			case FILE_PARAM:
839 			case ADDR_PARAM:
840 				/* do nothing */
841 			break;
842 
843 			default:
844 				phpdbg_error("Invalid parameter type for conditional breakpoint");
845 				return;
846 		}
847 	}
848 
849 	PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_COND);
850 	new_break.hash = hash;
851 
852 	if (param) {
853 		new_break.paramed = 1;
854 		phpdbg_copy_param(
855 			param, &new_break.param);
856 	    if (new_break.param.type == FILE_PARAM ||
857 	        new_break.param.type == NUMERIC_FILE_PARAM) {
858 	        char realpath[MAXPATHLEN];
859 
860 	        if (VCWD_REALPATH(new_break.param.file.name, realpath)) {
861 	            efree(new_break.param.file.name);
862 
863 	            new_break.param.file.name = estrdup(realpath);
864 	        } else {
865 	            phpdbg_error("Invalid file for conditional break %s", new_break.param.file.name);
866 	            phpdbg_clear_param(&new_break.param);
867 	            return;
868 	        }
869 	    }
870 	} else {
871 		new_break.paramed = 0;
872 	}
873 
874 	cops = CG(compiler_options);
875 
876 	CG(compiler_options) = ZEND_COMPILE_DEFAULT_FOR_EVAL;
877 
878 	new_break.code = estrndup(expr, expr_len);
879 	new_break.code_len = expr_len;
880 
881 	bp_code = zend_string_concat3(
882 		"return ", sizeof("return ")-1, expr, expr_len, ";", sizeof(";")-1);
883 	new_break.ops = zend_compile_string(bp_code, "Conditional Breakpoint Code", ZEND_COMPILE_POSITION_AFTER_OPEN_TAG);
884 	zend_string_release(bp_code);
885 
886 	if (new_break.ops) {
887 		brake = zend_hash_index_update_mem(&PHPDBG_G(bp)[PHPDBG_BREAK_COND], hash, &new_break, sizeof(phpdbg_breakcond_t));
888 
889 		phpdbg_notice("Conditional breakpoint #%d added %s/%p", brake->id, brake->code, brake->ops);
890 
891 		PHPDBG_G(flags) |= PHPDBG_HAS_COND_BP;
892 		PHPDBG_BREAK_MAPPING(new_break.id, &PHPDBG_G(bp)[PHPDBG_BREAK_COND]);
893 	} else {
894 		 phpdbg_error("Failed to compile code for expression %s", expr);
895 		 efree((char*)new_break.code);
896 		 PHPDBG_G(bp_count)--;
897 	}
898 
899 	CG(compiler_options) = cops;
900 } /* }}} */
901 
phpdbg_set_breakpoint_expression(const char * expr,size_t expr_len)902 PHPDBG_API void phpdbg_set_breakpoint_expression(const char *expr, size_t expr_len) /* {{{ */
903 {
904 	zend_ulong expr_hash = zend_hash_func(expr, expr_len);
905 	phpdbg_breakcond_t new_break;
906 
907 	if (!zend_hash_index_exists(&PHPDBG_G(bp)[PHPDBG_BREAK_COND], expr_hash)) {
908 		phpdbg_create_conditional_break(
909 			&new_break, NULL, expr, expr_len, expr_hash);
910 	} else {
911 		phpdbg_error("Conditional break %s exists", expr);
912 	}
913 } /* }}} */
914 
phpdbg_set_breakpoint_at(const phpdbg_param_t * param)915 PHPDBG_API void phpdbg_set_breakpoint_at(const phpdbg_param_t *param) /* {{{ */
916 {
917 	phpdbg_breakcond_t new_break;
918 	phpdbg_param_t *condition;
919 	zend_ulong hash = 0L;
920 
921 	if (param->next) {
922 		condition = param->next;
923 		hash = zend_hash_func(condition->str, condition->len);
924 
925 		if (!zend_hash_index_exists(&PHPDBG_G(bp)[PHPDBG_BREAK_COND], hash)) {
926 			phpdbg_create_conditional_break(&new_break, param, condition->str, condition->len, hash);
927 		} else {
928 			phpdbg_notice("Conditional break %s exists at the specified location", condition->str);
929 		}
930 	}
931 
932 } /* }}} */
933 
phpdbg_find_breakpoint_file(zend_op_array * op_array)934 static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_file(zend_op_array *op_array) /* {{{ */
935 {
936 	HashTable *breaks;
937 	phpdbg_breakbase_t *brake;
938 
939 #if 0
940 	phpdbg_debug("Op at: %.*s %d\n", ZSTR_LEN(op_array->filename), ZSTR_VAL(op_array->filename), (*EG(opline_ptr))->lineno);
941 #endif
942 
943 	/* NOTE: realpath resolution should have happened at compile time - no reason to do it here again */
944 	if (!(breaks = zend_hash_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE], op_array->filename))) {
945 		return NULL;
946 	}
947 
948 	if (EG(current_execute_data) && (brake = zend_hash_index_find_ptr(breaks, EG(current_execute_data)->opline->lineno))) {
949 		return brake;
950 	}
951 
952 	return NULL;
953 } /* }}} */
954 
phpdbg_find_breakpoint_symbol(zend_function * fbc)955 static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_symbol(zend_function *fbc) /* {{{ */
956 {
957 	zend_op_array *ops;
958 
959 	if (fbc->type != ZEND_USER_FUNCTION) {
960 		return NULL;
961 	}
962 
963 	ops = (zend_op_array *) fbc;
964 
965 	if (ops->scope) {
966 		/* find method breaks here */
967 		return phpdbg_find_breakpoint_method(ops);
968 	}
969 
970 	if (ops->function_name) {
971 		phpdbg_breakbase_t *brake;
972 		zend_string *fname = zend_string_tolower(ops->function_name);
973 
974 		brake = zend_hash_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], fname);
975 
976 		zend_string_release(fname);
977 		return brake;
978 	} else {
979 		return zend_hash_str_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], ZEND_STRL("main"));
980 	}
981 } /* }}} */
982 
phpdbg_find_breakpoint_method(zend_op_array * ops)983 static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_method(zend_op_array *ops) /* {{{ */
984 {
985 	HashTable *class_table;
986 	phpdbg_breakbase_t *brake = NULL;
987 	zend_string *class_lcname = zend_string_tolower(ops->scope->name);
988 
989 	if ((class_table = zend_hash_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD], class_lcname))) {
990 		zend_string *lcname = zend_string_tolower(ops->function_name);
991 
992 		brake = zend_hash_find_ptr(class_table, lcname);
993 
994 		zend_string_release(lcname);
995 	}
996 
997 	zend_string_release(class_lcname);
998 	return brake;
999 } /* }}} */
1000 
phpdbg_find_breakpoint_opline(phpdbg_opline_ptr_t opline)1001 static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_opline(phpdbg_opline_ptr_t opline) /* {{{ */
1002 {
1003 	phpdbg_breakline_t *brake;
1004 
1005 	if ((brake = zend_hash_index_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], (zend_ulong) opline)) && brake->base) {
1006 		return (phpdbg_breakbase_t *)brake->base;
1007 	}
1008 
1009 	return (phpdbg_breakbase_t *) brake;
1010 } /* }}} */
1011 
phpdbg_find_breakpoint_opcode(uint8_t opcode)1012 static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_opcode(uint8_t opcode) /* {{{ */
1013 {
1014 	const char *opname = zend_get_opcode_name(opcode);
1015 
1016 	if (!opname) {
1017 		return NULL;
1018 	}
1019 
1020 	return zend_hash_index_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_OPCODE], zend_hash_func(opname, strlen(opname)));
1021 } /* }}} */
1022 
phpdbg_find_breakpoint_param(phpdbg_param_t * param,zend_execute_data * execute_data)1023 static inline bool phpdbg_find_breakpoint_param(phpdbg_param_t *param, zend_execute_data *execute_data) /* {{{ */
1024 {
1025 	zend_function *function = execute_data->func;
1026 
1027 	switch (param->type) {
1028 		case NUMERIC_FUNCTION_PARAM:
1029 		case STR_PARAM: {
1030 			/* function breakpoint */
1031 
1032 			if (function->type != ZEND_USER_FUNCTION) {
1033 				return 0;
1034 			}
1035 
1036 			{
1037 				const char *str = NULL;
1038 				size_t len = 0L;
1039 				zend_op_array *ops = (zend_op_array*)function;
1040 				str = ops->function_name ? ZSTR_VAL(ops->function_name) : "main";
1041 				len = ops->function_name ? ZSTR_LEN(ops->function_name) : strlen(str);
1042 
1043 				if (len == param->len && memcmp(param->str, str, len) == SUCCESS) {
1044 					return param->type == STR_PARAM || execute_data->opline - ops->opcodes == param->num;
1045 				}
1046 			}
1047 		} break;
1048 
1049 		case FILE_PARAM: {
1050 			if (param->file.line == zend_get_executed_lineno()) {
1051 				const char *str = zend_get_executed_filename();
1052 				size_t lengths[2] = {strlen(param->file.name), strlen(str)};
1053 
1054 				if (lengths[0] == lengths[1]) {
1055 					return (memcmp(
1056 						param->file.name, str, lengths[0]) == SUCCESS);
1057 				}
1058 			}
1059 		} break;
1060 
1061 		case NUMERIC_METHOD_PARAM:
1062 		case METHOD_PARAM: {
1063 			if (function->type != ZEND_USER_FUNCTION) {
1064 				return 0;
1065 			}
1066 
1067 			{
1068 				zend_op_array *ops = (zend_op_array*) function;
1069 
1070 				if (ops->scope) {
1071 					size_t lengths[2] = { strlen(param->method.class), ZSTR_LEN(ops->scope->name) };
1072 					if (lengths[0] == lengths[1] && memcmp(param->method.class, ops->scope->name, lengths[0]) == SUCCESS) {
1073 						lengths[0] = strlen(param->method.name);
1074 						lengths[1] = ZSTR_LEN(ops->function_name);
1075 
1076 						if (lengths[0] == lengths[1] && memcmp(param->method.name, ops->function_name, lengths[0]) == SUCCESS) {
1077 							return param->type == METHOD_PARAM || (execute_data->opline - ops->opcodes) == param->num;
1078 						}
1079 					}
1080 				}
1081 			}
1082 		} break;
1083 
1084 		case ADDR_PARAM: {
1085 			return ((zend_ulong)(phpdbg_opline_ptr_t)execute_data->opline == param->addr);
1086 		} break;
1087 
1088 		default: {
1089 			/* do nothing */
1090 		} break;
1091 	}
1092 	return 0;
1093 } /* }}} */
1094 
phpdbg_find_conditional_breakpoint(zend_execute_data * execute_data)1095 static inline phpdbg_breakbase_t *phpdbg_find_conditional_breakpoint(zend_execute_data *execute_data) /* {{{ */
1096 {
1097 	phpdbg_breakcond_t *bp;
1098 	int breakpoint = FAILURE;
1099 
1100 	ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(bp)[PHPDBG_BREAK_COND], bp) {
1101 		zval retval;
1102 		const zend_op *orig_opline = EG(current_execute_data)->opline;
1103 		zend_function *orig_func = EG(current_execute_data)->func;
1104 		zval *orig_retval = EG(current_execute_data)->return_value;
1105 
1106 		if (((phpdbg_breakbase_t*)bp)->disabled) {
1107 			continue;
1108 		}
1109 
1110 		if (bp->paramed) {
1111 			if (!phpdbg_find_breakpoint_param(&bp->param, execute_data)) {
1112 				continue;
1113 			}
1114 		}
1115 
1116 		EG(no_extensions) = 1;
1117 
1118 		zend_rebuild_symbol_table();
1119 
1120 		zend_try {
1121 			PHPDBG_G(flags) |= PHPDBG_IN_COND_BP;
1122 			zend_execute(bp->ops, &retval);
1123 			if (zend_is_true(&retval)) {
1124 				breakpoint = SUCCESS;
1125 			}
1126  		} zend_end_try();
1127 
1128 		EG(no_extensions) = 1;
1129 		EG(current_execute_data)->opline = orig_opline;
1130 		EG(current_execute_data)->func = orig_func;
1131 		EG(current_execute_data)->return_value = orig_retval;
1132 		PHPDBG_G(flags) &= ~PHPDBG_IN_COND_BP;
1133 
1134 		if (breakpoint == SUCCESS) {
1135 			break;
1136 		}
1137 	} ZEND_HASH_FOREACH_END();
1138 
1139 	return (breakpoint == SUCCESS) ? ((phpdbg_breakbase_t *) bp) : NULL;
1140 } /* }}} */
1141 
phpdbg_find_breakpoint(zend_execute_data * execute_data)1142 PHPDBG_API phpdbg_breakbase_t *phpdbg_find_breakpoint(zend_execute_data *execute_data) /* {{{ */
1143 {
1144 	phpdbg_breakbase_t *base = NULL;
1145 
1146 	if (!(PHPDBG_G(flags) & PHPDBG_IS_BP_ENABLED)) {
1147 		return NULL;
1148 	}
1149 
1150 	/* conditions cannot be executed by eval()'d code */
1151 	if (!(PHPDBG_G(flags) & PHPDBG_IN_EVAL) &&
1152 		(PHPDBG_G(flags) & PHPDBG_HAS_COND_BP) &&
1153 		(base = phpdbg_find_conditional_breakpoint(execute_data))) {
1154 		goto result;
1155 	}
1156 
1157 	if ((PHPDBG_G(flags) & PHPDBG_HAS_FILE_BP) && (base = phpdbg_find_breakpoint_file(&execute_data->func->op_array))) {
1158 		goto result;
1159 	}
1160 
1161 	if (PHPDBG_G(flags) & (PHPDBG_HAS_METHOD_BP|PHPDBG_HAS_SYM_BP)) {
1162 		zend_op_array *op_array = &execute_data->func->op_array;
1163 		/* check we are at the beginning of the stack, but after argument RECV */
1164 		if (execute_data->opline == op_array->opcodes + op_array->num_args + !!(op_array->fn_flags & ZEND_ACC_VARIADIC)) {
1165 			if ((base = phpdbg_find_breakpoint_symbol(execute_data->func))) {
1166 				goto result;
1167 			}
1168 		}
1169 	}
1170 
1171 	if ((PHPDBG_G(flags) & PHPDBG_HAS_OPLINE_BP) && (base = phpdbg_find_breakpoint_opline((phpdbg_opline_ptr_t) execute_data->opline))) {
1172 		goto result;
1173 	}
1174 
1175 	if ((PHPDBG_G(flags) & PHPDBG_HAS_OPCODE_BP) && (base = phpdbg_find_breakpoint_opcode(execute_data->opline->opcode))) {
1176 		goto result;
1177 	}
1178 
1179 	return NULL;
1180 
1181 result:
1182 	/* we return nothing for disable breakpoints */
1183 	if (base->disabled) {
1184 		return NULL;
1185 	}
1186 
1187 	return base;
1188 } /* }}} */
1189 
phpdbg_delete_breakpoint(zend_ulong num)1190 PHPDBG_API void phpdbg_delete_breakpoint(zend_ulong num) /* {{{ */
1191 {
1192 	HashTable *table;
1193 	phpdbg_breakbase_t *brake;
1194 	zend_string *strkey;
1195 	zend_ulong numkey;
1196 
1197 	if ((brake = phpdbg_find_breakbase_ex(num, &table, &numkey, &strkey))) {
1198 		int type = brake->type;
1199 		char *name = NULL;
1200 		size_t name_len = 0L;
1201 
1202 		switch (type) {
1203 			case PHPDBG_BREAK_FILE:
1204 			case PHPDBG_BREAK_METHOD:
1205 				if (zend_hash_num_elements(table) == 1) {
1206 					name = estrdup(brake->name);
1207 					name_len = strlen(name);
1208 					if (zend_hash_num_elements(&PHPDBG_G(bp)[type]) == 1) {
1209 						PHPDBG_G(flags) &= ~(1<<(brake->type+1));
1210 					}
1211 				}
1212 			break;
1213 
1214 			default: {
1215 				if (zend_hash_num_elements(table) == 1) {
1216 					PHPDBG_G(flags) &= ~(1<<(brake->type+1));
1217 				}
1218 			}
1219 		}
1220 
1221 		switch (type) {
1222 			case PHPDBG_BREAK_FILE_OPLINE:
1223 			case PHPDBG_BREAK_FUNCTION_OPLINE:
1224 			case PHPDBG_BREAK_METHOD_OPLINE:
1225 				if (zend_hash_num_elements(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE]) == 1) {
1226 					PHPDBG_G(flags) &= PHPDBG_HAS_OPLINE_BP;
1227 				}
1228 				zend_hash_index_del(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], ((phpdbg_breakopline_t *) brake)->opline);
1229 		}
1230 
1231 		if (strkey) {
1232 			zend_hash_del(table, strkey);
1233 		} else {
1234 			zend_hash_index_del(table, numkey);
1235 		}
1236 
1237 		switch (type) {
1238 			case PHPDBG_BREAK_FILE:
1239 			case PHPDBG_BREAK_METHOD:
1240 				if (name) {
1241 					zend_hash_str_del(&PHPDBG_G(bp)[type], name, name_len);
1242 					efree(name);
1243 				}
1244 			break;
1245 		}
1246 
1247 		phpdbg_notice("Deleted breakpoint #"ZEND_ULONG_FMT, num);
1248 		PHPDBG_BREAK_UNMAPPING(num);
1249 	} else {
1250 		phpdbg_error("Failed to find breakpoint #"ZEND_ULONG_FMT, num);
1251 	}
1252 } /* }}} */
1253 
phpdbg_clear_breakpoints(void)1254 PHPDBG_API void phpdbg_clear_breakpoints(void) /* {{{ */
1255 {
1256 	zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE]);
1257 	zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_PENDING]);
1258 	zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM]);
1259 	zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE]);
1260 	zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD_OPLINE]);
1261 	zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_FUNCTION_OPLINE]);
1262 	zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE]);
1263 	zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_OPCODE]);
1264 	zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD]);
1265 	zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_COND]);
1266 	zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP]);
1267 
1268 	PHPDBG_G(flags) &= ~PHPDBG_BP_MASK;
1269 
1270 	PHPDBG_G(bp_count) = 0;
1271 } /* }}} */
1272 
phpdbg_hit_breakpoint(phpdbg_breakbase_t * brake,bool output)1273 PHPDBG_API void phpdbg_hit_breakpoint(phpdbg_breakbase_t *brake, bool output) /* {{{ */
1274 {
1275 	brake->hits++;
1276 
1277 	if (output) {
1278 		phpdbg_print_breakpoint(brake);
1279 	}
1280 } /* }}} */
1281 
phpdbg_print_breakpoint(phpdbg_breakbase_t * brake)1282 PHPDBG_API void phpdbg_print_breakpoint(phpdbg_breakbase_t *brake) /* {{{ */
1283 {
1284 	if (!brake)
1285 		goto unknown;
1286 
1287 	switch (brake->type) {
1288 		case PHPDBG_BREAK_FILE: {
1289 			phpdbg_notice("Breakpoint #%d at %s:"ZEND_ULONG_FMT", hits: "ZEND_ULONG_FMT"",
1290 				((phpdbg_breakfile_t*)brake)->id,
1291 				((phpdbg_breakfile_t*)brake)->filename,
1292 				((phpdbg_breakfile_t*)brake)->line,
1293 				((phpdbg_breakfile_t*)brake)->hits);
1294 		} break;
1295 
1296 		case PHPDBG_BREAK_SYM: {
1297 			phpdbg_notice("Breakpoint #%d in %s() at %s:%u, hits: "ZEND_ULONG_FMT"",
1298 				((phpdbg_breaksymbol_t*)brake)->id,
1299 				((phpdbg_breaksymbol_t*)brake)->symbol,
1300 				zend_get_executed_filename(),
1301 				zend_get_executed_lineno(),
1302 				((phpdbg_breakfile_t*)brake)->hits);
1303 		} break;
1304 
1305 		case PHPDBG_BREAK_OPLINE: {
1306 			phpdbg_notice("Breakpoint #%d in #"ZEND_ULONG_FMT" at %s:%u, hits: "ZEND_ULONG_FMT"",
1307 				((phpdbg_breakline_t*)brake)->id,
1308 				((phpdbg_breakline_t*)brake)->opline,
1309 				zend_get_executed_filename(),
1310 				zend_get_executed_lineno(),
1311 				((phpdbg_breakline_t*)brake)->hits);
1312 		} break;
1313 
1314 		case PHPDBG_BREAK_METHOD_OPLINE: {
1315 			 phpdbg_notice("Breakpoint #%d in %s::%s()#"ZEND_ULONG_FMT" at %s:%u, hits: "ZEND_ULONG_FMT"",
1316 				((phpdbg_breakopline_t*)brake)->id,
1317 				((phpdbg_breakopline_t*)brake)->class_name,
1318 				((phpdbg_breakopline_t*)brake)->func_name,
1319 				((phpdbg_breakopline_t*)brake)->opline_num,
1320 				zend_get_executed_filename(),
1321 				zend_get_executed_lineno(),
1322 				((phpdbg_breakopline_t*)brake)->hits);
1323 		} break;
1324 
1325 		case PHPDBG_BREAK_FUNCTION_OPLINE: {
1326 			 phpdbg_notice("Breakpoint #%d in %s()#"ZEND_ULONG_FMT" at %s:%u, hits: "ZEND_ULONG_FMT"",
1327 				((phpdbg_breakopline_t*)brake)->id,
1328 				((phpdbg_breakopline_t*)brake)->func_name,
1329 				((phpdbg_breakopline_t*)brake)->opline_num,
1330 				zend_get_executed_filename(),
1331 				zend_get_executed_lineno(),
1332 				((phpdbg_breakopline_t*)brake)->hits);
1333 		} break;
1334 
1335 		case PHPDBG_BREAK_FILE_OPLINE: {
1336 			 phpdbg_notice("Breakpoint #%d in #"ZEND_ULONG_FMT" at %s:%u, hits: "ZEND_ULONG_FMT"",
1337 				((phpdbg_breakopline_t*)brake)->id,
1338 				((phpdbg_breakopline_t*)brake)->opline_num,
1339 				zend_get_executed_filename(),
1340 				zend_get_executed_lineno(),
1341 				((phpdbg_breakopline_t*)brake)->hits);
1342 		} break;
1343 
1344 		case PHPDBG_BREAK_OPCODE: {
1345 			 phpdbg_notice("Breakpoint #%d in %s at %s:%u, hits: "ZEND_ULONG_FMT"",
1346 				((phpdbg_breakop_t*)brake)->id,
1347 				((phpdbg_breakop_t*)brake)->name,
1348 				zend_get_executed_filename(),
1349 				zend_get_executed_lineno(),
1350 				((phpdbg_breakop_t*)brake)->hits);
1351 		} break;
1352 
1353 		case PHPDBG_BREAK_METHOD: {
1354 			 phpdbg_notice("Breakpoint #%d in %s::%s() at %s:%u, hits: "ZEND_ULONG_FMT"",
1355 				((phpdbg_breakmethod_t*)brake)->id,
1356 				((phpdbg_breakmethod_t*)brake)->class_name,
1357 				((phpdbg_breakmethod_t*)brake)->func_name,
1358 				zend_get_executed_filename(),
1359 				zend_get_executed_lineno(),
1360 				((phpdbg_breakmethod_t*)brake)->hits);
1361 		} break;
1362 
1363 		case PHPDBG_BREAK_COND: {
1364 			if (((phpdbg_breakcond_t*)brake)->paramed) {
1365 				char *param;
1366 				phpdbg_notice("Conditional breakpoint #%d: at %s if %s at %s:%u, hits: "ZEND_ULONG_FMT"",
1367 					((phpdbg_breakcond_t*)brake)->id,
1368 					phpdbg_param_tostring(&((phpdbg_breakcond_t*)brake)->param, &param),
1369 					((phpdbg_breakcond_t*)brake)->code,
1370 					zend_get_executed_filename(),
1371 					zend_get_executed_lineno(),
1372 					((phpdbg_breakcond_t*)brake)->hits);
1373 				if (param)
1374 					free(param);
1375 			} else {
1376 				phpdbg_notice("Conditional breakpoint #%d: on %s == true at %s:%u, hits: "ZEND_ULONG_FMT"",
1377 					((phpdbg_breakcond_t*)brake)->id,
1378 					((phpdbg_breakcond_t*)brake)->code,
1379 					zend_get_executed_filename(),
1380 					zend_get_executed_lineno(),
1381 					((phpdbg_breakcond_t*)brake)->hits);
1382 			}
1383 
1384 		} break;
1385 
1386 		default: {
1387 unknown:
1388 			phpdbg_notice("Unknown breakpoint at %s:%u",
1389 				zend_get_executed_filename(),
1390 				zend_get_executed_lineno());
1391 		}
1392 	}
1393 } /* }}} */
1394 
phpdbg_enable_breakpoint(zend_ulong id)1395 PHPDBG_API void phpdbg_enable_breakpoint(zend_ulong id) /* {{{ */
1396 {
1397 	phpdbg_breakbase_t *brake = phpdbg_find_breakbase(id);
1398 
1399 	if (brake) {
1400 		brake->disabled = 0;
1401 	}
1402 } /* }}} */
1403 
phpdbg_disable_breakpoint(zend_ulong id)1404 PHPDBG_API void phpdbg_disable_breakpoint(zend_ulong id) /* {{{ */
1405 {
1406 	phpdbg_breakbase_t *brake = phpdbg_find_breakbase(id);
1407 
1408 	if (brake) {
1409 		brake->disabled = 1;
1410 	}
1411 } /* }}} */
1412 
phpdbg_enable_breakpoints(void)1413 PHPDBG_API void phpdbg_enable_breakpoints(void) /* {{{ */
1414 {
1415 	PHPDBG_G(flags) |= PHPDBG_IS_BP_ENABLED;
1416 } /* }}} */
1417 
phpdbg_disable_breakpoints(void)1418 PHPDBG_API void phpdbg_disable_breakpoints(void) { /* {{{ */
1419 	PHPDBG_G(flags) &= ~PHPDBG_IS_BP_ENABLED;
1420 } /* }}} */
1421 
phpdbg_find_breakbase(zend_ulong id)1422 PHPDBG_API phpdbg_breakbase_t *phpdbg_find_breakbase(zend_ulong id) /* {{{ */
1423 {
1424 	HashTable *table;
1425 	zend_string *strkey;
1426 	zend_ulong numkey;
1427 
1428 	return phpdbg_find_breakbase_ex(id, &table, &numkey, &strkey);
1429 } /* }}} */
1430 
phpdbg_find_breakbase_ex(zend_ulong id,HashTable ** table,zend_ulong * numkey,zend_string ** strkey)1431 PHPDBG_API phpdbg_breakbase_t *phpdbg_find_breakbase_ex(zend_ulong id, HashTable **table, zend_ulong *numkey, zend_string **strkey) /* {{{ */
1432 {
1433 	if ((*table = zend_hash_index_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP], id))) {
1434 		phpdbg_breakbase_t *brake;
1435 
1436 		ZEND_HASH_FOREACH_KEY_PTR(*table, *numkey, *strkey, brake) {
1437 			if (brake->id == id) {
1438 				return brake;
1439 			}
1440 		} ZEND_HASH_FOREACH_END();
1441 	}
1442 
1443 	return NULL;
1444 } /* }}} */
1445 
phpdbg_print_breakpoints(zend_ulong type)1446 PHPDBG_API void phpdbg_print_breakpoints(zend_ulong type) /* {{{ */
1447 {
1448 	switch (type) {
1449 		case PHPDBG_BREAK_SYM: if ((PHPDBG_G(flags) & PHPDBG_HAS_SYM_BP)) {
1450 			phpdbg_breaksymbol_t *brake;
1451 
1452 			phpdbg_out(SEPARATE "\n");
1453 			phpdbg_out("Function Breakpoints:\n");
1454 			ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], brake) {
1455 				phpdbg_writeln("#%d\t\t%s%s",
1456 					brake->id, brake->symbol,
1457 					((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : "");
1458 			} ZEND_HASH_FOREACH_END();
1459 		} break;
1460 
1461 		case PHPDBG_BREAK_METHOD: if ((PHPDBG_G(flags) & PHPDBG_HAS_METHOD_BP)) {
1462 			HashTable *class_table;
1463 
1464 			phpdbg_out(SEPARATE "\n");
1465 			phpdbg_out("Method Breakpoints:\n");
1466 			ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD], class_table) {
1467 				phpdbg_breakmethod_t *brake;
1468 
1469 				ZEND_HASH_MAP_FOREACH_PTR(class_table, brake) {
1470 					phpdbg_writeln("#%d\t\t%s::%s%s",
1471 						brake->id, brake->class_name, brake->func_name,
1472 						((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : "");
1473 				} ZEND_HASH_FOREACH_END();
1474 			} ZEND_HASH_FOREACH_END();
1475 		} break;
1476 
1477 		case PHPDBG_BREAK_FILE: if ((PHPDBG_G(flags) & PHPDBG_HAS_FILE_BP)) {
1478 			HashTable *points;
1479 
1480 			phpdbg_out(SEPARATE "\n");
1481 			phpdbg_out("File Breakpoints:\n");
1482 			ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE], points) {
1483 				phpdbg_breakfile_t *brake;
1484 
1485 				ZEND_HASH_MAP_FOREACH_PTR(points, brake) {
1486 					phpdbg_writeln("#%d\t\t%s:"ZEND_ULONG_FMT"%s",
1487 						brake->id, brake->filename, brake->line,
1488 						((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : "");
1489 				} ZEND_HASH_FOREACH_END();
1490 			} ZEND_HASH_FOREACH_END();
1491 		}  if ((PHPDBG_G(flags) & PHPDBG_HAS_PENDING_FILE_BP)) {
1492 			HashTable *points;
1493 
1494 			phpdbg_out(SEPARATE "\n");
1495 			phpdbg_out("Pending File Breakpoints:\n");
1496 			ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_PENDING], points) {
1497 				phpdbg_breakfile_t *brake;
1498 
1499 				ZEND_HASH_MAP_FOREACH_PTR(points, brake) {
1500 					phpdbg_writeln("#%d\t\t%s:"ZEND_ULONG_FMT"%s",
1501 						brake->id, brake->filename, brake->line,
1502 						((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : "");
1503 				} ZEND_HASH_FOREACH_END();
1504 			} ZEND_HASH_FOREACH_END();
1505 		} break;
1506 
1507 		case PHPDBG_BREAK_OPLINE: if ((PHPDBG_G(flags) & PHPDBG_HAS_OPLINE_BP)) {
1508 			phpdbg_breakline_t *brake;
1509 
1510 			phpdbg_out(SEPARATE "\n");
1511 			phpdbg_out("Opline Breakpoints:\n");
1512 			ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], brake) {
1513 				const char *type;
1514 				switch (brake->type) {
1515 					case PHPDBG_BREAK_METHOD_OPLINE:
1516 						type = "method";
1517 						goto print_opline;
1518 					case PHPDBG_BREAK_FUNCTION_OPLINE:
1519 						type = "function";
1520 						goto print_opline;
1521 					case PHPDBG_BREAK_FILE_OPLINE:
1522 						type = "method";
1523 
1524 					print_opline: {
1525 						if (brake->type == PHPDBG_BREAK_METHOD_OPLINE) {
1526 							type = "method";
1527 						} else if (brake->type == PHPDBG_BREAK_FUNCTION_OPLINE) {
1528 							type = "function";
1529 						} else if (brake->type == PHPDBG_BREAK_FILE_OPLINE) {
1530 							type = "file";
1531 						}
1532 
1533 						phpdbg_writeln("#%d\t\t#"ZEND_ULONG_FMT"\t\t(%s breakpoint)%s",
1534 							brake->id, brake->opline, type,
1535 							((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : "");
1536 					} break;
1537 
1538 					default:
1539 						phpdbg_writeln("#%d\t\t#"ZEND_ULONG_FMT"%s",
1540 							brake->id, brake->opline,
1541 							((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : "");
1542 						break;
1543 				}
1544 			} ZEND_HASH_FOREACH_END();
1545 		} break;
1546 
1547 		case PHPDBG_BREAK_METHOD_OPLINE: if ((PHPDBG_G(flags) & PHPDBG_HAS_METHOD_OPLINE_BP)) {
1548 			HashTable *class_table, *method_table;
1549 
1550 			phpdbg_out(SEPARATE "\n");
1551 			phpdbg_out("Method opline Breakpoints:\n");
1552 			ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD_OPLINE], class_table) {
1553 				ZEND_HASH_MAP_FOREACH_PTR(class_table, method_table) {
1554 					phpdbg_breakopline_t *brake;
1555 
1556 					ZEND_HASH_MAP_FOREACH_PTR(method_table, brake) {
1557 						phpdbg_writeln("#%d\t\t%s::%s opline "ZEND_ULONG_FMT"%s",
1558 							brake->id, brake->class_name, brake->func_name, brake->opline_num,
1559 							((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : "");
1560 					} ZEND_HASH_FOREACH_END();
1561 				} ZEND_HASH_FOREACH_END();
1562 			} ZEND_HASH_FOREACH_END();
1563 		} break;
1564 
1565 		case PHPDBG_BREAK_FUNCTION_OPLINE: if ((PHPDBG_G(flags) & PHPDBG_HAS_FUNCTION_OPLINE_BP)) {
1566 			HashTable *function_table;
1567 
1568 			phpdbg_out(SEPARATE "\n");
1569 			phpdbg_out("Function opline Breakpoints:\n");
1570 			ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(bp)[PHPDBG_BREAK_FUNCTION_OPLINE], function_table) {
1571 				phpdbg_breakopline_t *brake;
1572 
1573 				ZEND_HASH_MAP_FOREACH_PTR(function_table, brake) {
1574 					phpdbg_writeln("#%d\t\t%s opline "ZEND_ULONG_FMT"%s",
1575 						brake->id, brake->func_name, brake->opline_num,
1576 						((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : "");
1577 				} ZEND_HASH_FOREACH_END();
1578 			} ZEND_HASH_FOREACH_END();
1579 		} break;
1580 
1581 		case PHPDBG_BREAK_FILE_OPLINE: if ((PHPDBG_G(flags) & PHPDBG_HAS_FILE_OPLINE_BP)) {
1582 			HashTable *file_table;
1583 
1584 			phpdbg_out(SEPARATE "\n");
1585 			phpdbg_out("File opline Breakpoints:\n");
1586 			ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE], file_table) {
1587 				phpdbg_breakopline_t *brake;
1588 
1589 				ZEND_HASH_MAP_FOREACH_PTR(file_table, brake) {
1590 					phpdbg_writeln("#%d\t\t%s opline "ZEND_ULONG_FMT"%s",
1591 						brake->id, brake->class_name, brake->opline_num,
1592 						((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : "");
1593 				} ZEND_HASH_FOREACH_END();
1594 			} ZEND_HASH_FOREACH_END();
1595 		} break;
1596 
1597 		case PHPDBG_BREAK_COND: if ((PHPDBG_G(flags) & PHPDBG_HAS_COND_BP)) {
1598 			phpdbg_breakcond_t *brake;
1599 
1600 			phpdbg_out(SEPARATE "\n");
1601 			phpdbg_out("Conditional Breakpoints:\n");
1602 			ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(bp)[PHPDBG_BREAK_COND], brake) {
1603 				if (brake->paramed) {
1604 					switch (brake->param.type) {
1605 						case STR_PARAM:
1606 							phpdbg_writeln("#%d\t\tat %s if %s%s",
1607 				 				brake->id, brake->param.str, brake->code,
1608 				 				((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : "");
1609 						break;
1610 
1611 						case NUMERIC_FUNCTION_PARAM:
1612 							phpdbg_writeln("#%d\t\tat %s#"ZEND_ULONG_FMT" if %s%s",
1613 				 				brake->id, brake->param.str, brake->param.num, brake->code,
1614 				 				((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : "");
1615 						break;
1616 
1617 						case METHOD_PARAM:
1618 							phpdbg_writeln("#%d\t\tat %s::%s if %s%s",
1619 				 				brake->id, brake->param.method.class, brake->param.method.name, brake->code,
1620 				 				((phpdbg_breakbase_t*)brake)->disabled ? " [disabled]" : "");
1621 						break;
1622 
1623 						case NUMERIC_METHOD_PARAM:
1624 							phpdbg_writeln("#%d\t\tat %s::%s#"ZEND_ULONG_FMT" if %s%s",
1625 				 				brake->id, brake->param.method.class, brake->param.method.name, brake->param.num, brake->code,
1626 				 				((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : "");
1627 						break;
1628 
1629 						case FILE_PARAM:
1630 							phpdbg_writeln("#%d\t\tat %s:"ZEND_ULONG_FMT" if %s%s",
1631 				 				brake->id, brake->param.file.name, brake->param.file.line, brake->code,
1632 				 				((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : "");
1633 						break;
1634 
1635 						case ADDR_PARAM:
1636 							phpdbg_writeln("#%d\t\tat #"ZEND_ULONG_FMT" if %s%s",
1637 				 				brake->id, brake->param.addr, brake->code,
1638 				 				((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : "");
1639 						break;
1640 
1641 						default:
1642 							phpdbg_error("Invalid parameter type for conditional breakpoint");
1643 						return;
1644 					}
1645 				} else {
1646 					phpdbg_writeln("#%d\t\tif %s%s",
1647 				 		brake->id, brake->code,
1648 				 		((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : "");
1649 				}
1650 			} ZEND_HASH_FOREACH_END();
1651 		} break;
1652 
1653 		case PHPDBG_BREAK_OPCODE: if (PHPDBG_G(flags) & PHPDBG_HAS_OPCODE_BP) {
1654 			phpdbg_breakop_t *brake;
1655 
1656 			phpdbg_out(SEPARATE "\n");
1657 			phpdbg_out("Opcode Breakpoints:\n");
1658 			ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(bp)[PHPDBG_BREAK_OPCODE], brake) {
1659 				phpdbg_writeln("#%d\t\t%s%s",
1660 					brake->id, brake->name,
1661 					((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : "");
1662 			} ZEND_HASH_FOREACH_END();
1663 		} break;
1664 	}
1665 } /* }}} */
1666