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 | Author: |
14 +----------------------------------------------------------------------+
15 */
16
17 #include "php.h"
18 #include "php_test.h"
19 #include "observer.h"
20 #include "zend_observer.h"
21 #include "zend_smart_str.h"
22 #include "ext/standard/php_var.h"
23
24 static zend_observer_fcall_handlers observer_fcall_init(zend_execute_data *execute_data);
25
observer_show_opcode_in_user_handler(zend_execute_data * execute_data)26 static int observer_show_opcode_in_user_handler(zend_execute_data *execute_data)
27 {
28 if (ZT_G(observer_show_output)) {
29 php_printf("%*s<!-- opcode: '%s' in user handler -->\n", 2 * ZT_G(observer_nesting_depth), "", zend_get_opcode_name(EX(opline)->opcode));
30 }
31
32 return ZEND_USER_OPCODE_DISPATCH;
33 }
34
observer_set_user_opcode_handler(const char * opcode_names,user_opcode_handler_t handler)35 static void observer_set_user_opcode_handler(const char *opcode_names, user_opcode_handler_t handler)
36 {
37 const char *s = NULL, *e = opcode_names;
38
39 while (1) {
40 if (*e == ' ' || *e == ',' || *e == '\0') {
41 if (s) {
42 uint8_t opcode = zend_get_opcode_id(s, e - s);
43 if (opcode <= ZEND_VM_LAST_OPCODE) {
44 zend_set_user_opcode_handler(opcode, handler);
45 } else {
46 zend_error(E_WARNING, "Invalid opcode name %.*s", (int) (e - s), e);
47 }
48 s = NULL;
49 }
50 } else {
51 if (!s) {
52 s = e;
53 }
54 }
55 if (*e == '\0') {
56 break;
57 }
58 e++;
59 }
60 }
61
observer_show_opcode(zend_execute_data * execute_data)62 static void observer_show_opcode(zend_execute_data *execute_data)
63 {
64 if (!ZT_G(observer_show_opcode) || !ZEND_USER_CODE(EX(func)->type)) {
65 return;
66 }
67 php_printf("%*s<!-- opcode: '%s' -->\n", 2 * ZT_G(observer_nesting_depth), "", zend_get_opcode_name(EX(opline)->opcode));
68 }
69
assert_observer_opline(zend_execute_data * execute_data)70 static inline void assert_observer_opline(zend_execute_data *execute_data) {
71 ZEND_ASSERT(!ZEND_USER_CODE(EX(func)->type) ||
72 (EX(opline) >= EX(func)->op_array.opcodes && EX(opline) < EX(func)->op_array.opcodes + EX(func)->op_array.last) ||
73 (EX(opline) >= EG(exception_op) && EX(opline) < EG(exception_op) + 3));
74 }
75
observer_begin(zend_execute_data * execute_data)76 static void observer_begin(zend_execute_data *execute_data)
77 {
78 assert_observer_opline(execute_data);
79
80 if (!ZT_G(observer_show_output)) {
81 return;
82 }
83
84 if (execute_data->func && execute_data->func->common.function_name) {
85 if (execute_data->func->common.scope) {
86 php_printf("%*s<%s::%s>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->common.scope->name), ZSTR_VAL(execute_data->func->common.function_name));
87 } else {
88 php_printf("%*s<%s>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->common.function_name));
89 }
90 } else {
91 php_printf("%*s<file '%s'>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->op_array.filename));
92 }
93 ZT_G(observer_nesting_depth)++;
94 observer_show_opcode(execute_data);
95 }
96
get_retval_info(zval * retval,smart_str * buf)97 static void get_retval_info(zval *retval, smart_str *buf)
98 {
99 if (!ZT_G(observer_show_return_type) && !ZT_G(observer_show_return_value)) {
100 return;
101 }
102
103 smart_str_appendc(buf, ':');
104 if (retval == NULL) {
105 smart_str_appendl(buf, "NULL", 4);
106 } else if (ZT_G(observer_show_return_value)) {
107 if (Z_TYPE_P(retval) == IS_OBJECT) {
108 smart_str_appendl(buf, "object(", 7);
109 smart_str_append(buf, Z_OBJCE_P(retval)->name);
110 smart_str_appendl(buf, ")#", 2);
111 smart_str_append_long(buf, Z_OBJ_HANDLE_P(retval));
112 } else {
113 php_var_export_ex(retval, 2 * ZT_G(observer_nesting_depth) + 3, buf);
114 }
115 } else if (ZT_G(observer_show_return_type)) {
116 smart_str_appends(buf, zend_zval_type_name(retval));
117 }
118 smart_str_0(buf);
119 }
120
observer_end(zend_execute_data * execute_data,zval * retval)121 static void observer_end(zend_execute_data *execute_data, zval *retval)
122 {
123 assert_observer_opline(execute_data);
124
125 if (!ZT_G(observer_show_output)) {
126 return;
127 }
128
129 if (EG(exception)) {
130 php_printf("%*s<!-- Exception: %s -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(EG(exception)->ce->name));
131 }
132 observer_show_opcode(execute_data);
133 ZT_G(observer_nesting_depth)--;
134 if (execute_data->func && execute_data->func->common.function_name) {
135 smart_str retval_info = {0};
136 get_retval_info(retval, &retval_info);
137 if (execute_data->func->common.scope) {
138 php_printf("%*s</%s::%s%s>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->common.scope->name), ZSTR_VAL(execute_data->func->common.function_name), retval_info.s ? ZSTR_VAL(retval_info.s) : "");
139 } else {
140 php_printf("%*s</%s%s>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->common.function_name), retval_info.s ? ZSTR_VAL(retval_info.s) : "");
141 }
142 smart_str_free(&retval_info);
143 } else {
144 php_printf("%*s</file '%s'>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->op_array.filename));
145 }
146 }
147
observer_show_init(zend_function * fbc)148 static void observer_show_init(zend_function *fbc)
149 {
150 if (fbc->common.function_name) {
151 if (fbc->common.scope) {
152 php_printf("%*s<!-- init %s::%s() -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name));
153 } else {
154 php_printf("%*s<!-- init %s() -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.function_name));
155 }
156 } else {
157 php_printf("%*s<!-- init '%s' -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->op_array.filename));
158 }
159 }
160
observer_show_init_backtrace(zend_execute_data * execute_data)161 static void observer_show_init_backtrace(zend_execute_data *execute_data)
162 {
163 zend_execute_data *ex = execute_data;
164 php_printf("%*s<!--\n", 2 * ZT_G(observer_nesting_depth), "");
165 do {
166 zend_function *fbc = ex->func;
167 int indent = 2 * ZT_G(observer_nesting_depth) + 4;
168 if (fbc->common.function_name) {
169 if (fbc->common.scope) {
170 php_printf("%*s%s::%s()\n", indent, "", ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name));
171 } else {
172 php_printf("%*s%s()\n", indent, "", ZSTR_VAL(fbc->common.function_name));
173 }
174 } else {
175 php_printf("%*s{main} %s\n", indent, "", ZSTR_VAL(fbc->op_array.filename));
176 }
177 } while ((ex = ex->prev_execute_data) != NULL);
178 php_printf("%*s-->\n", 2 * ZT_G(observer_nesting_depth), "");
179 }
180
observer_fcall_init(zend_execute_data * execute_data)181 static zend_observer_fcall_handlers observer_fcall_init(zend_execute_data *execute_data)
182 {
183 zend_function *fbc = execute_data->func;
184 if (ZT_G(observer_show_output)) {
185 observer_show_init(fbc);
186 if (ZT_G(observer_show_init_backtrace)) {
187 observer_show_init_backtrace(execute_data);
188 }
189 observer_show_opcode(execute_data);
190 }
191
192 if (ZT_G(observer_observe_all)) {
193 return (zend_observer_fcall_handlers){observer_begin, observer_end};
194 } else if (fbc->common.function_name) {
195 if (ZT_G(observer_observe_functions)) {
196 return (zend_observer_fcall_handlers){observer_begin, observer_end};
197 } else if (zend_hash_exists(ZT_G(observer_observe_function_names), fbc->common.function_name)) {
198 return (zend_observer_fcall_handlers){observer_begin, observer_end};
199 }
200 } else {
201 if (ZT_G(observer_observe_includes)) {
202 return (zend_observer_fcall_handlers){observer_begin, observer_end};
203 }
204 }
205 return (zend_observer_fcall_handlers){NULL, NULL};
206 }
207
fiber_init_observer(zend_fiber_context * initializing)208 static void fiber_init_observer(zend_fiber_context *initializing) {
209 if (ZT_G(observer_fiber_init)) {
210 php_printf("<!-- alloc: %p -->\n", initializing);
211 }
212 }
213
fiber_destroy_observer(zend_fiber_context * destroying)214 static void fiber_destroy_observer(zend_fiber_context *destroying) {
215 if (ZT_G(observer_fiber_destroy)) {
216 php_printf("<!-- destroy: %p -->\n", destroying);
217 }
218 }
219
fiber_address_observer(zend_fiber_context * from,zend_fiber_context * to)220 static void fiber_address_observer(zend_fiber_context *from, zend_fiber_context *to)
221 {
222 if (ZT_G(observer_fiber_switch)) {
223 php_printf("<!-- switching from fiber %p to %p -->\n", from, to);
224 }
225 }
226
fiber_enter_observer(zend_fiber_context * from,zend_fiber_context * to)227 static void fiber_enter_observer(zend_fiber_context *from, zend_fiber_context *to)
228 {
229 if (ZT_G(observer_fiber_switch)) {
230 if (to->status == ZEND_FIBER_STATUS_INIT) {
231 php_printf("<init '%p'>\n", to);
232 } else if (to->kind == zend_ce_fiber) {
233 zend_fiber *fiber = zend_fiber_from_context(to);
234 if (fiber->caller != from) {
235 return;
236 }
237
238 if (fiber->flags & ZEND_FIBER_FLAG_DESTROYED) {
239 php_printf("<destroying '%p'>\n", to);
240 } else if (to->status != ZEND_FIBER_STATUS_DEAD) {
241 php_printf("<resume '%p'>\n", to);
242 }
243 }
244 }
245 }
246
fiber_suspend_observer(zend_fiber_context * from,zend_fiber_context * to)247 static void fiber_suspend_observer(zend_fiber_context *from, zend_fiber_context *to)
248 {
249 if (ZT_G(observer_fiber_switch)) {
250 if (from->status == ZEND_FIBER_STATUS_DEAD) {
251 zend_fiber *fiber = (from->kind == zend_ce_fiber) ? zend_fiber_from_context(from) : NULL;
252
253 if (fiber && fiber->flags & ZEND_FIBER_FLAG_THREW) {
254 php_printf("<threw '%p'>\n", from);
255 } else if (fiber && fiber->flags & ZEND_FIBER_FLAG_DESTROYED) {
256 php_printf("<destroyed '%p'>\n", from);
257 } else {
258 php_printf("<returned '%p'>\n", from);
259 }
260 } else if (from->kind == zend_ce_fiber) {
261 zend_fiber *fiber = zend_fiber_from_context(from);
262 if (fiber->caller == NULL) {
263 php_printf("<suspend '%p'>\n", from);
264 }
265 }
266 }
267 }
268
declared_function_observer(zend_op_array * op_array,zend_string * name)269 void declared_function_observer(zend_op_array *op_array, zend_string *name) {
270 if (ZT_G(observer_observe_declaring)) {
271 php_printf("%*s<!-- declared function '%s' -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(name));
272 }
273 }
274
declared_class_observer(zend_class_entry * ce,zend_string * name)275 void declared_class_observer(zend_class_entry *ce, zend_string *name) {
276 if (ZT_G(observer_observe_declaring)) {
277 php_printf("%*s<!-- declared class '%s' -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(name));
278 }
279 }
280
281 static void (*zend_test_prev_execute_internal)(zend_execute_data *execute_data, zval *return_value);
zend_test_execute_internal(zend_execute_data * execute_data,zval * return_value)282 static void zend_test_execute_internal(zend_execute_data *execute_data, zval *return_value) {
283 zend_function *fbc = execute_data->func;
284
285 if (fbc->common.function_name) {
286 if (fbc->common.scope) {
287 php_printf("%*s<!-- internal enter %s::%s() -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name));
288 } else {
289 php_printf("%*s<!-- internal enter %s() -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.function_name));
290 }
291 } else {
292 php_printf("%*s<!-- internal enter '%s' -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->op_array.filename));
293 }
294
295 if (zend_test_prev_execute_internal) {
296 zend_test_prev_execute_internal(execute_data, return_value);
297 } else {
298 fbc->internal_function.handler(execute_data, return_value);
299 }
300 }
301
ZEND_INI_MH(zend_test_observer_OnUpdateCommaList)302 static ZEND_INI_MH(zend_test_observer_OnUpdateCommaList)
303 {
304 zend_array **p = (zend_array **) ZEND_INI_GET_ADDR();
305 zend_string *funcname;
306 zend_function *func;
307 if (stage != PHP_INI_STAGE_STARTUP && stage != PHP_INI_STAGE_ACTIVATE && stage != PHP_INI_STAGE_DEACTIVATE && stage != PHP_INI_STAGE_SHUTDOWN) {
308 ZEND_HASH_FOREACH_STR_KEY(*p, funcname) {
309 if ((func = zend_hash_find_ptr(EG(function_table), funcname))) {
310 void *old_handler;
311 zend_observer_remove_begin_handler(func, observer_begin, (zend_observer_fcall_begin_handler *)&old_handler);
312 zend_observer_remove_end_handler(func, observer_end, (zend_observer_fcall_end_handler *)&old_handler);
313 }
314 } ZEND_HASH_FOREACH_END();
315 }
316 zend_hash_clean(*p);
317 if (new_value && ZSTR_LEN(new_value)) {
318 const char *start = ZSTR_VAL(new_value), *ptr;
319 while ((ptr = strchr(start, ','))) {
320 zend_string *str = zend_string_init(start, ptr - start, 1);
321 GC_MAKE_PERSISTENT_LOCAL(str);
322 zend_hash_add_empty_element(*p, str);
323 zend_string_release(str);
324 start = ptr + 1;
325 }
326 zend_string *str = zend_string_init(start, ZSTR_VAL(new_value) + ZSTR_LEN(new_value) - start, 1);
327 GC_MAKE_PERSISTENT_LOCAL(str);
328 zend_hash_add_empty_element(*p, str);
329 zend_string_release(str);
330 if (stage != PHP_INI_STAGE_STARTUP && stage != PHP_INI_STAGE_ACTIVATE && stage != PHP_INI_STAGE_DEACTIVATE && stage != PHP_INI_STAGE_SHUTDOWN) {
331 ZEND_HASH_FOREACH_STR_KEY(*p, funcname) {
332 if ((func = zend_hash_find_ptr(EG(function_table), funcname))) {
333 zend_observer_add_begin_handler(func, observer_begin);
334 zend_observer_add_end_handler(func, observer_end);
335 }
336 } ZEND_HASH_FOREACH_END();
337 }
338 }
339 return SUCCESS;
340 }
341
342 PHP_INI_BEGIN()
343 STD_PHP_INI_BOOLEAN("zend_test.observer.enabled", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_enabled, zend_zend_test_globals, zend_test_globals)
344 STD_PHP_INI_BOOLEAN("zend_test.observer.show_output", "1", PHP_INI_SYSTEM, OnUpdateBool, observer_show_output, zend_zend_test_globals, zend_test_globals)
345 STD_PHP_INI_BOOLEAN("zend_test.observer.observe_all", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_all, zend_zend_test_globals, zend_test_globals)
346 STD_PHP_INI_BOOLEAN("zend_test.observer.observe_includes", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_includes, zend_zend_test_globals, zend_test_globals)
347 STD_PHP_INI_BOOLEAN("zend_test.observer.observe_functions", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_functions, zend_zend_test_globals, zend_test_globals)
348 STD_PHP_INI_BOOLEAN("zend_test.observer.observe_declaring", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_declaring, zend_zend_test_globals, zend_test_globals)
349 STD_PHP_INI_ENTRY("zend_test.observer.observe_function_names", "", PHP_INI_ALL, zend_test_observer_OnUpdateCommaList, observer_observe_function_names, zend_zend_test_globals, zend_test_globals)
350 STD_PHP_INI_BOOLEAN("zend_test.observer.show_return_type", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_return_type, zend_zend_test_globals, zend_test_globals)
351 STD_PHP_INI_BOOLEAN("zend_test.observer.show_return_value", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_return_value, zend_zend_test_globals, zend_test_globals)
352 STD_PHP_INI_BOOLEAN("zend_test.observer.show_init_backtrace", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_init_backtrace, zend_zend_test_globals, zend_test_globals)
353 STD_PHP_INI_BOOLEAN("zend_test.observer.show_opcode", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_opcode, zend_zend_test_globals, zend_test_globals)
354 STD_PHP_INI_ENTRY("zend_test.observer.show_opcode_in_user_handler", "", PHP_INI_SYSTEM, OnUpdateString, observer_show_opcode_in_user_handler, zend_zend_test_globals, zend_test_globals)
355 STD_PHP_INI_BOOLEAN("zend_test.observer.fiber_init", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_fiber_init, zend_zend_test_globals, zend_test_globals)
356 STD_PHP_INI_BOOLEAN("zend_test.observer.fiber_switch", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_fiber_switch, zend_zend_test_globals, zend_test_globals)
357 STD_PHP_INI_BOOLEAN("zend_test.observer.fiber_destroy", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_fiber_destroy, zend_zend_test_globals, zend_test_globals)
358 STD_PHP_INI_BOOLEAN("zend_test.observer.execute_internal", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_execute_internal, zend_zend_test_globals, zend_test_globals)
PHP_INI_END()359 PHP_INI_END()
360
361 void zend_test_observer_init(INIT_FUNC_ARGS)
362 {
363 // Loading via dl() not supported with the observer API
364 if (type != MODULE_TEMPORARY) {
365 REGISTER_INI_ENTRIES();
366 if (ZT_G(observer_enabled)) {
367 zend_observer_fcall_register(observer_fcall_init);
368 }
369 } else {
370 (void)ini_entries;
371 }
372
373 if (ZT_G(observer_enabled) && ZT_G(observer_show_opcode_in_user_handler)) {
374 observer_set_user_opcode_handler(ZT_G(observer_show_opcode_in_user_handler), observer_show_opcode_in_user_handler);
375 }
376
377 if (ZT_G(observer_enabled)) {
378 zend_observer_fiber_init_register(fiber_init_observer);
379 zend_observer_fiber_switch_register(fiber_address_observer);
380 zend_observer_fiber_switch_register(fiber_enter_observer);
381 zend_observer_fiber_switch_register(fiber_suspend_observer);
382 zend_observer_fiber_destroy_register(fiber_destroy_observer);
383
384 zend_observer_function_declared_register(declared_function_observer);
385 zend_observer_class_linked_register(declared_class_observer);
386 }
387
388 if (ZT_G(observer_execute_internal)) {
389 zend_test_prev_execute_internal = zend_execute_internal;
390 zend_execute_internal = zend_test_execute_internal;
391 }
392 }
393
zend_test_observer_shutdown(SHUTDOWN_FUNC_ARGS)394 void zend_test_observer_shutdown(SHUTDOWN_FUNC_ARGS)
395 {
396 if (type != MODULE_TEMPORARY) {
397 UNREGISTER_INI_ENTRIES();
398 }
399 }
400
zend_test_observer_ginit(zend_zend_test_globals * zend_test_globals)401 void zend_test_observer_ginit(zend_zend_test_globals *zend_test_globals) {
402 zend_test_globals->observer_observe_function_names = malloc(sizeof(HashTable));
403 _zend_hash_init(zend_test_globals->observer_observe_function_names, 8, ZVAL_PTR_DTOR, 1);
404 GC_MAKE_PERSISTENT_LOCAL(zend_test_globals->observer_observe_function_names);
405 }
406
zend_test_observer_gshutdown(zend_zend_test_globals * zend_test_globals)407 void zend_test_observer_gshutdown(zend_zend_test_globals *zend_test_globals) {
408 zend_hash_release(zend_test_globals->observer_observe_function_names);
409 }
410