xref: /PHP-8.3/sapi/phpdbg/phpdbg_info.c (revision 422aa17b)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Felipe Pena <felipe@php.net>                                |
14    | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
15    | Authors: Bob Weinand <bwoebi@php.net>                                |
16    +----------------------------------------------------------------------+
17 */
18 
19 #include "php.h"
20 #include "phpdbg.h"
21 #include "phpdbg_utils.h"
22 #include "phpdbg_info.h"
23 #include "phpdbg_bp.h"
24 #include "phpdbg_prompt.h"
25 
26 ZEND_EXTERN_MODULE_GLOBALS(phpdbg)
27 
28 #define PHPDBG_INFO_COMMAND_D(f, h, a, m, l, s, flags) \
29 	PHPDBG_COMMAND_D_EXP(f, h, a, m, l, s, &phpdbg_prompt_commands[13], flags)
30 
31 const phpdbg_command_t phpdbg_info_commands[] = {
32 	PHPDBG_INFO_COMMAND_D(break,     "show breakpoints",              'b', info_break,     NULL, 0, PHPDBG_ASYNC_SAFE),
33 	PHPDBG_INFO_COMMAND_D(files,     "show included files",           'F', info_files,     NULL, 0, PHPDBG_ASYNC_SAFE),
34 	PHPDBG_INFO_COMMAND_D(classes,   "show loaded classes",           'c', info_classes,   NULL, 0, PHPDBG_ASYNC_SAFE),
35 	PHPDBG_INFO_COMMAND_D(funcs,     "show loaded classes",           'f', info_funcs,     NULL, 0, PHPDBG_ASYNC_SAFE),
36 	PHPDBG_INFO_COMMAND_D(error,     "show last error",               'e', info_error,     NULL, 0, PHPDBG_ASYNC_SAFE),
37 	PHPDBG_INFO_COMMAND_D(constants, "show user defined constants",   'd', info_constants, NULL, 0, PHPDBG_ASYNC_SAFE),
38 	PHPDBG_INFO_COMMAND_D(vars,      "show active variables",         'v', info_vars,      NULL, 0, PHPDBG_ASYNC_SAFE),
39 	PHPDBG_INFO_COMMAND_D(globals,   "show superglobals",             'g', info_globals,   NULL, 0, PHPDBG_ASYNC_SAFE),
40 	PHPDBG_INFO_COMMAND_D(literal,   "show active literal constants", 'l', info_literal,   NULL, 0, PHPDBG_ASYNC_SAFE),
41 	PHPDBG_INFO_COMMAND_D(memory,    "show memory manager stats",     'm', info_memory,    NULL, 0, PHPDBG_ASYNC_SAFE),
42 	PHPDBG_END_COMMAND
43 };
44 
PHPDBG_INFO(break)45 PHPDBG_INFO(break) /* {{{ */
46 {
47 	phpdbg_print_breakpoints(PHPDBG_BREAK_FILE);
48 	phpdbg_print_breakpoints(PHPDBG_BREAK_SYM);
49 	phpdbg_print_breakpoints(PHPDBG_BREAK_METHOD);
50 	phpdbg_print_breakpoints(PHPDBG_BREAK_OPLINE);
51 	phpdbg_print_breakpoints(PHPDBG_BREAK_FILE_OPLINE);
52 	phpdbg_print_breakpoints(PHPDBG_BREAK_FUNCTION_OPLINE);
53 	phpdbg_print_breakpoints(PHPDBG_BREAK_METHOD_OPLINE);
54 	phpdbg_print_breakpoints(PHPDBG_BREAK_COND);
55 	phpdbg_print_breakpoints(PHPDBG_BREAK_OPCODE);
56 
57 	return SUCCESS;
58 } /* }}} */
59 
PHPDBG_INFO(files)60 PHPDBG_INFO(files) /* {{{ */
61 {
62 	zend_string *fname;
63 
64 	phpdbg_try_access {
65 		phpdbg_notice("Included files: %d", zend_hash_num_elements(&EG(included_files)));
66 	} phpdbg_catch_access {
67 		phpdbg_error("Could not fetch included file count, invalid data source");
68 		return SUCCESS;
69 	} phpdbg_end_try_access();
70 
71 	phpdbg_try_access {
72 		ZEND_HASH_MAP_FOREACH_STR_KEY(&EG(included_files), fname) {
73 			phpdbg_writeln("File: %s", ZSTR_VAL(fname));
74 		} ZEND_HASH_FOREACH_END();
75 	} phpdbg_catch_access {
76 		phpdbg_error("Could not fetch file name, invalid data source, aborting included file listing");
77 	} phpdbg_end_try_access();
78 
79 	return SUCCESS;
80 } /* }}} */
81 
PHPDBG_INFO(error)82 PHPDBG_INFO(error) /* {{{ */
83 {
84 	if (PG(last_error_message)) {
85 		phpdbg_try_access {
86 			phpdbg_writeln("Last error: %s at %s line %d",
87 			    ZSTR_VAL(PG(last_error_message)),
88 			    ZSTR_VAL(PG(last_error_file)),
89 			    PG(last_error_lineno));
90 		} phpdbg_catch_access {
91 			phpdbg_notice("No error found!");
92 		} phpdbg_end_try_access();
93 	} else {
94 		phpdbg_notice("No error found!");
95 	}
96 	return SUCCESS;
97 } /* }}} */
98 
PHPDBG_INFO(constants)99 PHPDBG_INFO(constants) /* {{{ */
100 {
101 	HashTable consts;
102 	zend_constant *data;
103 
104 	zend_hash_init(&consts, 8, NULL, NULL, 0);
105 
106 	if (EG(zend_constants)) {
107 		phpdbg_try_access {
108 			ZEND_HASH_MAP_FOREACH_PTR(EG(zend_constants), data) {
109 				if (ZEND_CONSTANT_MODULE_NUMBER(data) == PHP_USER_CONSTANT) {
110 					zend_hash_update_ptr(&consts, data->name, data);
111 				}
112 			} ZEND_HASH_FOREACH_END();
113 		} phpdbg_catch_access {
114 			phpdbg_error("Cannot fetch all the constants, invalid data source");
115 		} phpdbg_end_try_access();
116 	}
117 
118 	phpdbg_notice("User-defined constants (%d)", zend_hash_num_elements(&consts));
119 
120 	if (zend_hash_num_elements(&consts)) {
121 		phpdbg_out("Address            Refs    Type      Constant\n");
122 		ZEND_HASH_MAP_FOREACH_PTR(&consts, data) {
123 
124 #define VARIABLEINFO(msg, ...) \
125 	phpdbg_writeln( \
126 		"%-18p %-7d %-9s %.*s" msg, &data->value, \
127 		Z_REFCOUNTED(data->value) ? Z_REFCOUNT(data->value) : 1, \
128 		zend_get_type_by_const(Z_TYPE(data->value)), \
129 		(int) ZSTR_LEN(data->name), ZSTR_VAL(data->name), ##__VA_ARGS__)
130 
131 			switch (Z_TYPE(data->value)) {
132 				case IS_STRING:
133 					phpdbg_try_access {
134 						VARIABLEINFO("\nstring (%zd) \"%.*s%s\"", Z_STRLEN(data->value), Z_STRLEN(data->value) < 255 ? (int) Z_STRLEN(data->value) : 255, Z_STRVAL(data->value), Z_STRLEN(data->value) > 255 ? "..." : "");
135 					} phpdbg_catch_access {
136 						VARIABLEINFO("");
137 					} phpdbg_end_try_access();
138 					break;
139 				case IS_TRUE:
140 					VARIABLEINFO("\nbool (true)");
141 					break;
142 				case IS_FALSE:
143 					VARIABLEINFO("\nbool (false)");
144 					break;
145 				case IS_LONG:
146 					VARIABLEINFO("\nint ("ZEND_LONG_FMT")", Z_LVAL(data->value));
147 					break;
148 				case IS_DOUBLE:
149 					VARIABLEINFO("\ndouble (%lf)", Z_DVAL(data->value));
150 					break;
151 				default:
152 					VARIABLEINFO("");
153 
154 #undef VARIABLEINFO
155 			}
156 		} ZEND_HASH_FOREACH_END();
157 	}
158 
159 	return SUCCESS;
160 } /* }}} */
161 
phpdbg_arm_auto_global(zval * ptrzv)162 static int phpdbg_arm_auto_global(zval *ptrzv) {
163 	zend_auto_global *auto_global = Z_PTR_P(ptrzv);
164 
165 	if (auto_global->armed) {
166 		if (PHPDBG_G(flags) & PHPDBG_IN_SIGNAL_HANDLER) {
167 			phpdbg_notice("Cannot show information about superglobal variable %.*s", (int) ZSTR_LEN(auto_global->name), ZSTR_VAL(auto_global->name));
168 		} else {
169 			auto_global->armed = auto_global->auto_global_callback(auto_global->name);
170 		}
171 	}
172 
173 	return 0;
174 }
175 
phpdbg_print_symbols(bool show_globals)176 static int phpdbg_print_symbols(bool show_globals) {
177 	HashTable vars;
178 	zend_array *symtable;
179 	zend_string *var;
180 	zval *data;
181 
182 	if (!EG(current_execute_data) || !EG(current_execute_data)->func) {
183 		phpdbg_error("No active op array!");
184 		return SUCCESS;
185 	}
186 
187 	if (show_globals) {
188 		/* that array should only be manipulated during init, so safe for async access during execution */
189 		zend_hash_apply(CG(auto_globals), (apply_func_t) phpdbg_arm_auto_global);
190 		symtable = &EG(symbol_table);
191 	} else if (!(symtable = zend_rebuild_symbol_table())) {
192 		phpdbg_error("No active symbol table!");
193 		return SUCCESS;
194 	}
195 
196 	zend_hash_init(&vars, 8, NULL, NULL, 0);
197 
198 	phpdbg_try_access {
199 		ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(symtable, var, data) {
200 			if (zend_is_auto_global(var) ^ !show_globals) {
201 				zend_hash_update(&vars, var, data);
202 			}
203 		} ZEND_HASH_FOREACH_END();
204 	} phpdbg_catch_access {
205 		phpdbg_error("Cannot fetch all data from the symbol table, invalid data source");
206 	} phpdbg_end_try_access();
207 
208 	if (show_globals) {
209 		phpdbg_notice("Superglobal variables (%d)", zend_hash_num_elements(&vars));
210 	} else {
211 		zend_op_array *ops = &EG(current_execute_data)->func->op_array;
212 
213 		if (ops->function_name) {
214 			if (ops->scope) {
215 				phpdbg_notice("Variables in %s::%s() (%d)", ops->scope->name->val, ops->function_name->val, zend_hash_num_elements(&vars));
216 			} else {
217 				phpdbg_notice("Variables in %s() (%d)", ZSTR_VAL(ops->function_name), zend_hash_num_elements(&vars));
218 			}
219 		} else {
220 			if (ops->filename) {
221 				phpdbg_notice("Variables in %s (%d)", ZSTR_VAL(ops->filename), zend_hash_num_elements(&vars));
222 			} else {
223 				phpdbg_notice("Variables @ %p (%d)", ops, zend_hash_num_elements(&vars));
224 			}
225 		}
226 	}
227 
228 	if (zend_hash_num_elements(&vars)) {
229 		phpdbg_out("Address            Refs    Type      Variable\n");
230 		ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(&vars, var, data) {
231 			phpdbg_try_access {
232 				const char *isref = "";
233 #define VARIABLEINFO(msg, ...) \
234 	phpdbg_writeln( \
235 		"%-18p %-7d %-9s %s$%.*s" msg, data, Z_REFCOUNTED_P(data) ? Z_REFCOUNT_P(data) : 1, zend_get_type_by_const(Z_TYPE_P(data)), isref, (int) ZSTR_LEN(var), ZSTR_VAL(var), ##__VA_ARGS__)
236 retry_switch:
237 				switch (Z_TYPE_P(data)) {
238 					case IS_RESOURCE:
239 						phpdbg_try_access {
240 							const char *type = zend_rsrc_list_get_rsrc_type(Z_RES_P(data));
241 							VARIABLEINFO("\n|-------(typeof)------> (%s)\n", type ? type : "unknown");
242 						} phpdbg_catch_access {
243 							VARIABLEINFO("\n|-------(typeof)------> (unknown)\n");
244 						} phpdbg_end_try_access();
245 						break;
246 					case IS_OBJECT:
247 						phpdbg_try_access {
248 							VARIABLEINFO("\n|-----(instanceof)----> (%s)\n", ZSTR_VAL(Z_OBJCE_P(data)->name));
249 						} phpdbg_catch_access {
250 							VARIABLEINFO("\n|-----(instanceof)----> (unknown)\n");
251 						} phpdbg_end_try_access();
252 						break;
253 					case IS_STRING:
254 						phpdbg_try_access {
255 							VARIABLEINFO("\nstring (%zd) \"%.*s%s\"", Z_STRLEN_P(data), Z_STRLEN_P(data) < 255 ? (int) Z_STRLEN_P(data) : 255, Z_STRVAL_P(data), Z_STRLEN_P(data) > 255 ? "..." : "");
256 						} phpdbg_catch_access {
257 							VARIABLEINFO("");
258 						} phpdbg_end_try_access();
259 						break;
260 					case IS_TRUE:
261 						VARIABLEINFO("\nbool (true)");
262 						break;
263 					case IS_FALSE:
264 						VARIABLEINFO("\nbool (false)");
265 						break;
266 					case IS_LONG:
267 						VARIABLEINFO("\nint ("ZEND_LONG_FMT")", Z_LVAL_P(data));
268 						break;
269 					case IS_DOUBLE:
270 						VARIABLEINFO("\ndouble (%lf)", Z_DVAL_P(data));
271 						break;
272 					case IS_REFERENCE:
273 						isref = "&";
274 						data = Z_REFVAL_P(data);
275 						goto retry_switch;
276 					case IS_INDIRECT:
277 						data = Z_INDIRECT_P(data);
278 						goto retry_switch;
279 					default:
280 						VARIABLEINFO("");
281 				}
282 #undef VARIABLEINFO
283 			} phpdbg_catch_access {
284 				phpdbg_writeln("%p\tn/a\tn/a\t$%s", data, ZSTR_VAL(var));
285 			} phpdbg_end_try_access();
286 		} ZEND_HASH_FOREACH_END();
287 	}
288 
289 	zend_hash_destroy(&vars);
290 
291 	return SUCCESS;
292 } /* }}} */
293 
PHPDBG_INFO(vars)294 PHPDBG_INFO(vars) /* {{{ */
295 {
296 	return phpdbg_print_symbols(0);
297 }
298 
PHPDBG_INFO(globals)299 PHPDBG_INFO(globals) /* {{{ */
300 {
301 	return phpdbg_print_symbols(1);
302 }
303 
PHPDBG_INFO(literal)304 PHPDBG_INFO(literal) /* {{{ */
305 {
306 	/* literals are assumed to not be manipulated during executing of their op_array and as such async safe */
307 	bool in_executor = PHPDBG_G(in_execution) && EG(current_execute_data) && EG(current_execute_data)->func;
308 	if (in_executor || PHPDBG_G(ops)) {
309 		zend_op_array *ops = in_executor ? &EG(current_execute_data)->func->op_array : PHPDBG_G(ops);
310 		int literal = 0, count = ops->last_literal - 1;
311 
312 		if (ops->function_name) {
313 			if (ops->scope) {
314 				phpdbg_notice("Literal Constants in %s::%s() (%d)", ops->scope->name->val, ops->function_name->val, count);
315 			} else {
316 				phpdbg_notice("Literal Constants in %s() (%d)", ops->function_name->val, count);
317 			}
318 		} else {
319 			if (ops->filename) {
320 				phpdbg_notice("Literal Constants in %s (%d)", ZSTR_VAL(ops->filename), count);
321 			} else {
322 				phpdbg_notice("Literal Constants @ %p (%d)", ops, count);
323 			}
324 		}
325 
326 		while (literal < ops->last_literal) {
327 			if (Z_TYPE(ops->literals[literal]) != IS_NULL) {
328 				phpdbg_write("|-------- C%u -------> [", literal);
329 				zend_print_zval(&ops->literals[literal], 0);
330 				phpdbg_out("]\n");
331 			}
332 			literal++;
333 		}
334 	} else {
335 		phpdbg_error("Not executing!");
336 	}
337 
338 	return SUCCESS;
339 } /* }}} */
340 
PHPDBG_INFO(memory)341 PHPDBG_INFO(memory) /* {{{ */
342 {
343 	size_t used, real, peak_used, peak_real;
344 	zend_mm_heap *orig_heap = NULL;
345 	bool is_mm;
346 
347 	if (PHPDBG_G(flags) & PHPDBG_IN_SIGNAL_HANDLER) {
348 		orig_heap = zend_mm_set_heap(phpdbg_original_heap_sigsafe_mem());
349 	}
350 	if ((is_mm = is_zend_mm())) {
351 		used = zend_memory_usage(0);
352 		real = zend_memory_usage(1);
353 		peak_used = zend_memory_peak_usage(0);
354 		peak_real = zend_memory_peak_usage(1);
355 	}
356 	if (orig_heap) {
357 		zend_mm_set_heap(orig_heap);
358 	}
359 
360 	if (is_mm) {
361 		phpdbg_notice("Memory Manager Information");
362 		phpdbg_notice("Current");
363 		phpdbg_writeln( "|-------> Used:\t%.3f kB", (float) (used / 1024));
364 		phpdbg_writeln("|-------> Real:\t%.3f kB", (float) (real / 1024));
365 		phpdbg_notice("Peak");
366 		phpdbg_writeln("|-------> Used:\t%.3f kB", (float) (peak_used / 1024));
367 		phpdbg_writeln("|-------> Real:\t%.3f kB", (float) (peak_real / 1024));
368 	} else {
369 		phpdbg_error("Memory Manager Disabled!");
370 	}
371 	return SUCCESS;
372 } /* }}} */
373 
phpdbg_print_class_name(zend_class_entry * ce)374 static inline void phpdbg_print_class_name(zend_class_entry *ce) /* {{{ */
375 {
376 	const char *visibility = ce->type == ZEND_USER_CLASS ? "User" : "Internal";
377 	const char *type = (ce->ce_flags & ZEND_ACC_INTERFACE) ? "Interface" : (ce->ce_flags & ZEND_ACC_ABSTRACT) ? "Abstract Class" : "Class";
378 
379 	phpdbg_writeln("%s %s %.*s (%d)", visibility, type, (int) ZSTR_LEN(ce->name), ZSTR_VAL(ce->name), zend_hash_num_elements(&ce->function_table));
380 } /* }}} */
381 
PHPDBG_INFO(classes)382 PHPDBG_INFO(classes) /* {{{ */
383 {
384 	zend_class_entry *ce;
385 	HashTable classes;
386 
387 	zend_hash_init(&classes, 8, NULL, NULL, 0);
388 
389 	phpdbg_try_access {
390 		ZEND_HASH_MAP_FOREACH_PTR(EG(class_table), ce) {
391 			if (ce->type == ZEND_USER_CLASS) {
392 				zend_hash_next_index_insert_ptr(&classes, ce);
393 			}
394 		} ZEND_HASH_FOREACH_END();
395 	} phpdbg_catch_access {
396 		phpdbg_notice("Not all classes could be fetched, possibly invalid data source");
397 	} phpdbg_end_try_access();
398 
399 	phpdbg_notice("User Classes (%d)", zend_hash_num_elements(&classes));
400 
401 	/* once added, assume that classes are stable... until shutdown. */
402 	if (HT_IS_INITIALIZED(&classes)) {
403 		ZEND_HASH_PACKED_FOREACH_PTR(&classes, ce) {
404 			phpdbg_print_class_name(ce);
405 
406 			if (ce->parent) {
407 				if (ce->ce_flags & ZEND_ACC_LINKED) {
408 					zend_class_entry *pce = ce->parent;
409 					do {
410 						phpdbg_out("|-------- ");
411 						phpdbg_print_class_name(pce);
412 					} while ((pce = pce->parent));
413 				} else {
414 					phpdbg_writeln("|-------- User Class %s (not yet linked because declaration for parent was not encountered when declaring the class)", ZSTR_VAL(ce->parent_name));
415 				}
416 			}
417 
418 			if (ce->info.user.filename) {
419 				phpdbg_writeln("|---- in %s on line %u", ZSTR_VAL(ce->info.user.filename), ce->info.user.line_start);
420 			} else {
421 				phpdbg_writeln("|---- no source code");
422 			}
423 		} ZEND_HASH_FOREACH_END();
424 	}
425 
426 	zend_hash_destroy(&classes);
427 
428 	return SUCCESS;
429 } /* }}} */
430 
PHPDBG_INFO(funcs)431 PHPDBG_INFO(funcs) /* {{{ */
432 {
433 	zend_function *zf;
434 	HashTable functions;
435 
436 	zend_hash_init(&functions, 8, NULL, NULL, 0);
437 
438 	phpdbg_try_access {
439 		ZEND_HASH_MAP_FOREACH_PTR(EG(function_table), zf) {
440 			if (zf->type == ZEND_USER_FUNCTION) {
441 				zend_hash_next_index_insert_ptr(&functions, zf);
442 			}
443 		} ZEND_HASH_FOREACH_END();
444 	} phpdbg_catch_access {
445 		phpdbg_notice("Not all functions could be fetched, possibly invalid data source");
446 	} phpdbg_end_try_access();
447 
448 	phpdbg_notice("User Functions (%d)", zend_hash_num_elements(&functions));
449 
450 	if (HT_IS_INITIALIZED(&functions)) {
451 		ZEND_HASH_PACKED_FOREACH_PTR(&functions, zf) {
452 			zend_op_array *op_array = &zf->op_array;
453 
454 			phpdbg_write("|-------- %s", op_array->function_name ? ZSTR_VAL(op_array->function_name) : "{main}");
455 
456 			if (op_array->filename) {
457 				phpdbg_writeln(" in %s on line %d", ZSTR_VAL(op_array->filename), op_array->line_start);
458 			} else {
459 				phpdbg_writeln(" (no source code)");
460 			}
461 		} ZEND_HASH_FOREACH_END();
462 	}
463 
464 	zend_hash_destroy(&functions);
465 
466 	return SUCCESS;
467 } /* }}} */
468