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