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