1 /*
2    +----------------------------------------------------------------------+
3    | Zend Engine                                                          |
4    +----------------------------------------------------------------------+
5    | Copyright (c) Zend Technologies Ltd. (http://www.zend.com)           |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 2.00 of the Zend license,     |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.zend.com/license/2_00.txt.                                |
11    | If you did not receive a copy of the Zend license and are unable to  |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@zend.com so we can mail you a copy immediately.              |
14    +----------------------------------------------------------------------+
15    | Authors: Levi Morrison <levim@php.net>                               |
16    |          Sammy Kaye Powers <sammyk@php.net>                          |
17    +----------------------------------------------------------------------+
18 */
19 
20 #include "zend_observer.h"
21 
22 #include "zend_extensions.h"
23 #include "zend_llist.h"
24 #include "zend_vm.h"
25 
26 #define ZEND_OBSERVER_DATA(op_array) \
27 	ZEND_OP_ARRAY_EXTENSION(op_array, zend_observer_fcall_op_array_extension)
28 
29 #define ZEND_OBSERVER_NOT_OBSERVED ((void *) 2)
30 
31 #define ZEND_OBSERVABLE_FN(fn_flags) \
32 	(!(fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))
33 
34 typedef struct _zend_observer_fcall_data {
35 	// points after the last handler
36 	zend_observer_fcall_handlers *end;
37 	// a variadic array using "struct hack"
38 	zend_observer_fcall_handlers handlers[1];
39 } zend_observer_fcall_data;
40 
41 zend_llist zend_observers_fcall_list;
42 zend_llist zend_observer_error_callbacks;
43 zend_llist zend_observer_fiber_init;
44 zend_llist zend_observer_fiber_switch;
45 zend_llist zend_observer_fiber_destroy;
46 
47 int zend_observer_fcall_op_array_extension;
48 
49 ZEND_TLS zend_arena *fcall_handlers_arena;
50 ZEND_TLS zend_execute_data *first_observed_frame;
51 ZEND_TLS zend_execute_data *current_observed_frame;
52 
53 // Call during minit/startup ONLY
zend_observer_fcall_register(zend_observer_fcall_init init)54 ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init init) {
55 	if (!ZEND_OBSERVER_ENABLED) {
56 		/* We don't want to get an extension handle unless an ext installs an observer */
57 		zend_observer_fcall_op_array_extension =
58 			zend_get_op_array_extension_handle("Zend Observer");
59 
60 		/* ZEND_CALL_TRAMPOLINE has SPEC(OBSERVER) but zend_init_call_trampoline_op()
61 		 * is called before any extensions have registered as an observer. So we
62 		 * adjust the offset to the observed handler when we know we need to observe. */
63 		ZEND_VM_SET_OPCODE_HANDLER(&EG(call_trampoline_op));
64 
65 		/* ZEND_HANDLE_EXCEPTION also has SPEC(OBSERVER) and no observer extensions
66 		 * exist when zend_init_exception_op() is called. */
67 		ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op));
68 		ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op)+1);
69 		ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op)+2);
70 	}
71 	zend_llist_add_element(&zend_observers_fcall_list, &init);
72 }
73 
74 // Called by engine before MINITs
zend_observer_startup(void)75 ZEND_API void zend_observer_startup(void) {
76 	zend_llist_init(&zend_observers_fcall_list, sizeof(zend_observer_fcall_init), NULL, 1);
77 	zend_llist_init(&zend_observer_error_callbacks, sizeof(zend_observer_error_cb), NULL, 1);
78 	zend_llist_init(&zend_observer_fiber_init, sizeof(zend_observer_fiber_init_handler), NULL, 1);
79 	zend_llist_init(&zend_observer_fiber_switch, sizeof(zend_observer_fiber_switch_handler), NULL, 1);
80 	zend_llist_init(&zend_observer_fiber_destroy, sizeof(zend_observer_fiber_destroy_handler), NULL, 1);
81 
82 	zend_observer_fcall_op_array_extension = -1;
83 }
84 
zend_observer_activate(void)85 ZEND_API void zend_observer_activate(void) {
86 	if (ZEND_OBSERVER_ENABLED) {
87 		fcall_handlers_arena = zend_arena_create(4096);
88 	} else {
89 		fcall_handlers_arena = NULL;
90 	}
91 	first_observed_frame = NULL;
92 	current_observed_frame = NULL;
93 }
94 
zend_observer_deactivate(void)95 ZEND_API void zend_observer_deactivate(void) {
96 	if (fcall_handlers_arena) {
97 		zend_arena_destroy(fcall_handlers_arena);
98 	}
99 }
100 
zend_observer_shutdown(void)101 ZEND_API void zend_observer_shutdown(void) {
102 	zend_llist_destroy(&zend_observers_fcall_list);
103 	zend_llist_destroy(&zend_observer_error_callbacks);
104 	zend_llist_destroy(&zend_observer_fiber_switch);
105 }
106 
zend_observer_fcall_install(zend_execute_data * execute_data)107 static void zend_observer_fcall_install(zend_execute_data *execute_data) {
108 	zend_llist_element *element;
109 	zend_llist *list = &zend_observers_fcall_list;
110 	zend_function *function = execute_data->func;
111 	zend_op_array *op_array = &function->op_array;
112 
113 	if (fcall_handlers_arena == NULL) {
114 		return;
115 	}
116 
117 	ZEND_ASSERT(function->type != ZEND_INTERNAL_FUNCTION);
118 
119 	zend_llist handlers_list;
120 	zend_llist_init(&handlers_list, sizeof(zend_observer_fcall_handlers), NULL, 0);
121 	for (element = list->head; element; element = element->next) {
122 		zend_observer_fcall_init init;
123 		memcpy(&init, element->data, sizeof init);
124 		zend_observer_fcall_handlers handlers = init(execute_data);
125 		if (handlers.begin || handlers.end) {
126 			zend_llist_add_element(&handlers_list, &handlers);
127 		}
128 	}
129 
130 	ZEND_ASSERT(RUN_TIME_CACHE(op_array));
131 	void *ext;
132 	if (handlers_list.count) {
133 		size_t size = sizeof(zend_observer_fcall_data) + (handlers_list.count - 1) * sizeof(zend_observer_fcall_handlers);
134 		zend_observer_fcall_data *fcall_data = zend_arena_alloc(&fcall_handlers_arena, size);
135 		zend_observer_fcall_handlers *handlers = fcall_data->handlers;
136 		for (element = handlers_list.head; element; element = element->next) {
137 			memcpy(handlers++, element->data, sizeof *handlers);
138 		}
139 		fcall_data->end = handlers;
140 		ext = fcall_data;
141 	} else {
142 		ext = ZEND_OBSERVER_NOT_OBSERVED;
143 	}
144 
145 	ZEND_OBSERVER_DATA(op_array) = ext;
146 	zend_llist_destroy(&handlers_list);
147 }
148 
_zend_observe_fcall_begin(zend_execute_data * execute_data)149 static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_data)
150 {
151 	zend_op_array *op_array;
152 	uint32_t fn_flags;
153 	zend_observer_fcall_data *fcall_data;
154 	zend_observer_fcall_handlers *handlers, *end;
155 
156 	if (!ZEND_OBSERVER_ENABLED) {
157 		return;
158 	}
159 
160 	op_array = &execute_data->func->op_array;
161 	fn_flags = op_array->fn_flags;
162 
163 	if (!ZEND_OBSERVABLE_FN(fn_flags)) {
164 		return;
165 	}
166 
167 	fcall_data = ZEND_OBSERVER_DATA(op_array);
168 	if (!fcall_data) {
169 		zend_observer_fcall_install(execute_data);
170 		fcall_data = ZEND_OBSERVER_DATA(op_array);
171 	}
172 
173 	ZEND_ASSERT(fcall_data);
174 	if (fcall_data == ZEND_OBSERVER_NOT_OBSERVED) {
175 		return;
176 	}
177 
178 	if (first_observed_frame == NULL) {
179 		first_observed_frame = execute_data;
180 	}
181 	current_observed_frame = execute_data;
182 
183 	end = fcall_data->end;
184 	for (handlers = fcall_data->handlers; handlers != end; ++handlers) {
185 		if (handlers->begin) {
186 			handlers->begin(execute_data);
187 		}
188 	}
189 }
190 
zend_observer_generator_resume(zend_execute_data * execute_data)191 ZEND_API void ZEND_FASTCALL zend_observer_generator_resume(zend_execute_data *execute_data)
192 {
193 	_zend_observe_fcall_begin(execute_data);
194 }
195 
zend_observer_fcall_begin(zend_execute_data * execute_data)196 ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin(zend_execute_data *execute_data)
197 {
198 	ZEND_ASSUME(execute_data->func);
199 	if (!(execute_data->func->common.fn_flags & ZEND_ACC_GENERATOR)) {
200 		_zend_observe_fcall_begin(execute_data);
201 	}
202 }
203 
zend_observer_fcall_end(zend_execute_data * execute_data,zval * return_value)204 ZEND_API void ZEND_FASTCALL zend_observer_fcall_end(
205 	zend_execute_data *execute_data,
206 	zval *return_value)
207 {
208 	zend_function *func = execute_data->func;
209 	zend_observer_fcall_data *fcall_data;
210 	zend_observer_fcall_handlers *handlers, *end;
211 
212 	if (!ZEND_OBSERVER_ENABLED
213 	 || !ZEND_OBSERVABLE_FN(func->common.fn_flags)) {
214 		return;
215 	}
216 
217 	fcall_data = (zend_observer_fcall_data*)ZEND_OBSERVER_DATA(&func->op_array);
218 	// TODO: Fix exceptions from generators
219 	// ZEND_ASSERT(fcall_data);
220 	if (!fcall_data || fcall_data == ZEND_OBSERVER_NOT_OBSERVED) {
221 		return;
222 	}
223 
224 	handlers = fcall_data->end;
225 	end = fcall_data->handlers;
226 	while (handlers-- != end) {
227 		if (handlers->end) {
228 			handlers->end(execute_data, return_value);
229 		}
230 	}
231 
232 	if (first_observed_frame == execute_data) {
233 		first_observed_frame = NULL;
234 		current_observed_frame = NULL;
235 	} else {
236 		zend_execute_data *ex = execute_data->prev_execute_data;
237 		while (ex && (!ex->func || ex->func->type == ZEND_INTERNAL_FUNCTION
238 		          || !ZEND_OBSERVABLE_FN(ex->func->common.fn_flags)
239 		          || !ZEND_OBSERVER_DATA(&ex->func->op_array)
240 		          || ZEND_OBSERVER_DATA(&ex->func->op_array) == ZEND_OBSERVER_NOT_OBSERVED)) {
241 			ex = ex->prev_execute_data;
242 		}
243 		current_observed_frame = ex;
244 	}
245 }
246 
zend_observer_fcall_end_all(void)247 ZEND_API void zend_observer_fcall_end_all(void)
248 {
249 	zend_execute_data *ex = current_observed_frame;
250 	while (ex != NULL) {
251 		if (ex->func && ex->func->type != ZEND_INTERNAL_FUNCTION) {
252 			zend_observer_fcall_end(ex, NULL);
253 		}
254 		ex = ex->prev_execute_data;
255 	}
256 	current_observed_frame = NULL;
257 }
258 
zend_observer_error_register(zend_observer_error_cb cb)259 ZEND_API void zend_observer_error_register(zend_observer_error_cb cb)
260 {
261 	zend_llist_add_element(&zend_observer_error_callbacks, &cb);
262 }
263 
zend_observer_error_notify(int type,zend_string * error_filename,uint32_t error_lineno,zend_string * message)264 void zend_observer_error_notify(int type, zend_string *error_filename, uint32_t error_lineno, zend_string *message)
265 {
266 	zend_llist_element *element;
267 	zend_observer_error_cb callback;
268 
269 	for (element = zend_observer_error_callbacks.head; element; element = element->next) {
270 		callback = *(zend_observer_error_cb *) (element->data);
271 		callback(type, error_filename, error_lineno, message);
272 	}
273 }
274 
zend_observer_fiber_init_register(zend_observer_fiber_init_handler handler)275 ZEND_API void zend_observer_fiber_init_register(zend_observer_fiber_init_handler handler)
276 {
277 	zend_llist_add_element(&zend_observer_fiber_init, &handler);
278 }
279 
zend_observer_fiber_switch_register(zend_observer_fiber_switch_handler handler)280 ZEND_API void zend_observer_fiber_switch_register(zend_observer_fiber_switch_handler handler)
281 {
282 	zend_llist_add_element(&zend_observer_fiber_switch, &handler);
283 }
284 
zend_observer_fiber_destroy_register(zend_observer_fiber_destroy_handler handler)285 ZEND_API void zend_observer_fiber_destroy_register(zend_observer_fiber_destroy_handler handler)
286 {
287 	zend_llist_add_element(&zend_observer_fiber_destroy, &handler);
288 }
289 
zend_observer_fiber_init_notify(zend_fiber_context * initializing)290 ZEND_API void ZEND_FASTCALL zend_observer_fiber_init_notify(zend_fiber_context *initializing)
291 {
292 	zend_llist_element *element;
293 	zend_observer_fiber_init_handler callback;
294 
295 	for (element = zend_observer_fiber_init.head; element; element = element->next) {
296 		callback = *(zend_observer_fiber_init_handler *) element->data;
297 		callback(initializing);
298 	}
299 }
300 
zend_observer_fiber_switch_notify(zend_fiber_context * from,zend_fiber_context * to)301 ZEND_API void ZEND_FASTCALL zend_observer_fiber_switch_notify(zend_fiber_context *from, zend_fiber_context *to)
302 {
303 	zend_llist_element *element;
304 	zend_observer_fiber_switch_handler callback;
305 
306 	for (element = zend_observer_fiber_switch.head; element; element = element->next) {
307 		callback = *(zend_observer_fiber_switch_handler *) element->data;
308 		callback(from, to);
309 	}
310 }
311 
zend_observer_fiber_destroy_notify(zend_fiber_context * destroying)312 ZEND_API void ZEND_FASTCALL zend_observer_fiber_destroy_notify(zend_fiber_context *destroying)
313 {
314 	zend_llist_element *element;
315 	zend_observer_fiber_destroy_handler callback;
316 
317 	for (element = zend_observer_fiber_destroy.head; element; element = element->next) {
318 		callback = *(zend_observer_fiber_destroy_handler *) element->data;
319 		callback(destroying);
320 	}
321 }
322