xref: /PHP-7.4/ext/mysqlnd/mysqlnd_debug.c (revision efdbc368)
1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) The PHP Group                                          |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors: Andrey Hristov <andrey@php.net>                             |
16   |          Ulf Wendel <uw@php.net>                                     |
17   +----------------------------------------------------------------------+
18 */
19 
20 #include "php.h"
21 #include "mysqlnd.h"
22 #include "mysqlnd_priv.h"
23 #include "mysqlnd_debug.h"
24 
25 static const char * const mysqlnd_debug_default_trace_file = "/tmp/mysqlnd.trace";
26 static const char * const mysqlnd_debug_empty_string = "";
27 
28 
29 /* {{{ mysqlnd_debug::open */
30 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,open)31 MYSQLND_METHOD(mysqlnd_debug, open)(MYSQLND_DEBUG * self, zend_bool reopen)
32 {
33 	if (!self->file_name) {
34 		return FAIL;
35 	}
36 
37 	self->stream = php_stream_open_wrapper(self->file_name,
38 										   reopen == TRUE || self->flags & MYSQLND_DEBUG_APPEND? "ab":"wb",
39 										   REPORT_ERRORS, NULL);
40 	return self->stream? PASS:FAIL;
41 }
42 /* }}} */
43 
44 
45 /* {{{ mysqlnd_debug::log */
46 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,log)47 MYSQLND_METHOD(mysqlnd_debug, log)(MYSQLND_DEBUG * self,
48 								   unsigned int line, const char * const file,
49 								   unsigned int level, const char * type, const char * message)
50 {
51 	char pipe_buffer[512];
52 	enum_func_status ret;
53 	int i;
54 	char * message_line;
55 	unsigned int message_line_len;
56 	unsigned int flags = self->flags;
57 	char pid_buffer[10], time_buffer[30], file_buffer[200],
58 		 line_buffer[6], level_buffer[7];
59 
60 	if (!self->stream && FAIL == self->m->open(self, FALSE)) {
61 		return FAIL;
62 	}
63 
64 	if (level == -1) {
65 		level = zend_stack_count(&self->call_stack);
66 	}
67 	i = MIN(level, sizeof(pipe_buffer) / 2  - 1);
68 	pipe_buffer[i*2] = '\0';
69 	for (;i > 0;i--) {
70 		pipe_buffer[i*2 - 1] = ' ';
71 		pipe_buffer[i*2 - 2] = '|';
72 	}
73 
74 
75 	if (flags & MYSQLND_DEBUG_DUMP_PID) {
76 		snprintf(pid_buffer, sizeof(pid_buffer) - 1, "%5u: ", self->pid);
77 		pid_buffer[sizeof(pid_buffer) - 1 ] = '\0';
78 	}
79 	if (flags & MYSQLND_DEBUG_DUMP_TIME) {
80 		/* The following from FF's DBUG library, which is in the public domain */
81 #if defined(PHP_WIN32)
82 		/* FIXME This doesn't give microseconds as in Unix case, and the resolution is
83 		in system ticks, 10 ms intervals. See my_getsystime.c for high res */
84 		SYSTEMTIME loc_t;
85 		GetLocalTime(&loc_t);
86 		snprintf(time_buffer, sizeof(time_buffer) - 1,
87 				 /* "%04d-%02d-%02d " */
88 				 "%02d:%02d:%02d.%06d ",
89 				 /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/
90 				 loc_t.wHour, loc_t.wMinute, loc_t.wSecond, loc_t.wMilliseconds);
91 		time_buffer[sizeof(time_buffer) - 1 ] = '\0';
92 #else
93 		struct timeval tv;
94 		struct tm *tm_p;
95 		if (gettimeofday(&tv, NULL) != -1) {
96 			if ((tm_p= localtime((const time_t *)&tv.tv_sec))) {
97 				snprintf(time_buffer, sizeof(time_buffer) - 1,
98 						 /* "%04d-%02d-%02d " */
99 						 "%02d:%02d:%02d.%06d ",
100 						 /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/
101 						 tm_p->tm_hour, tm_p->tm_min, tm_p->tm_sec,
102 						 (int) (tv.tv_usec));
103 				time_buffer[sizeof(time_buffer) - 1 ] = '\0';
104 			}
105 		}
106 #endif
107 	}
108 	if (flags & MYSQLND_DEBUG_DUMP_FILE) {
109 		snprintf(file_buffer, sizeof(file_buffer) - 1, "%14s: ", file);
110 		file_buffer[sizeof(file_buffer) - 1 ] = '\0';
111 	}
112 	if (flags & MYSQLND_DEBUG_DUMP_LINE) {
113 		snprintf(line_buffer, sizeof(line_buffer) - 1, "%5u: ", line);
114 		line_buffer[sizeof(line_buffer) - 1 ] = '\0';
115 	}
116 	if (flags & MYSQLND_DEBUG_DUMP_LEVEL) {
117 		snprintf(level_buffer, sizeof(level_buffer) - 1, "%4u: ", level);
118 		level_buffer[sizeof(level_buffer) - 1 ] = '\0';
119 	}
120 
121 	message_line_len = mnd_sprintf(&message_line, 0, "%s%s%s%s%s%s%s%s\n",
122 								flags & MYSQLND_DEBUG_DUMP_PID? pid_buffer:"",
123 								flags & MYSQLND_DEBUG_DUMP_TIME? time_buffer:"",
124 								flags & MYSQLND_DEBUG_DUMP_FILE? file_buffer:"",
125 								flags & MYSQLND_DEBUG_DUMP_LINE? line_buffer:"",
126 								flags & MYSQLND_DEBUG_DUMP_LEVEL? level_buffer:"",
127 								pipe_buffer, type? type:"", message);
128 
129 	ret = php_stream_write(self->stream, message_line, message_line_len)? PASS:FAIL;
130 	mnd_sprintf_free(message_line);
131 	if (flags & MYSQLND_DEBUG_FLUSH) {
132 		self->m->close(self);
133 		self->m->open(self, TRUE);
134 	}
135 	return ret;
136 }
137 /* }}} */
138 
139 
140 /* {{{ mysqlnd_debug::log_va */
141 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,log_va)142 MYSQLND_METHOD(mysqlnd_debug, log_va)(MYSQLND_DEBUG *self,
143 									  unsigned int line, const char * const file,
144 									  unsigned int level, const char * type,
145 									  const char *format, ...)
146 {
147 	char pipe_buffer[512];
148 	int i;
149 	enum_func_status ret;
150 	char * message_line, *buffer;
151 	unsigned int message_line_len;
152 	va_list args;
153 	unsigned int flags = self->flags;
154 	char pid_buffer[10], time_buffer[30], file_buffer[200],
155 		 line_buffer[6], level_buffer[7];
156 
157 	if (!self->stream && FAIL == self->m->open(self, FALSE)) {
158 		return FAIL;
159 	}
160 
161 	if (level == -1) {
162 		level = zend_stack_count(&self->call_stack);
163 	}
164 	i = MIN(level, sizeof(pipe_buffer) / 2  - 1);
165 	pipe_buffer[i*2] = '\0';
166 	for (;i > 0;i--) {
167 		pipe_buffer[i*2 - 1] = ' ';
168 		pipe_buffer[i*2 - 2] = '|';
169 	}
170 
171 
172 	if (flags & MYSQLND_DEBUG_DUMP_PID) {
173 		snprintf(pid_buffer, sizeof(pid_buffer) - 1, "%5u: ", self->pid);
174 		pid_buffer[sizeof(pid_buffer) - 1 ] = '\0';
175 	}
176 	if (flags & MYSQLND_DEBUG_DUMP_TIME) {
177 		/* The following from FF's DBUG library, which is in the public domain */
178 #if defined(PHP_WIN32)
179 		/* FIXME This doesn't give microseconds as in Unix case, and the resolution is
180 		in system ticks, 10 ms intervals. See my_getsystime.c for high res */
181 		SYSTEMTIME loc_t;
182 		GetLocalTime(&loc_t);
183 		snprintf(time_buffer, sizeof(time_buffer) - 1,
184 				 /* "%04d-%02d-%02d " */
185 				 "%02d:%02d:%02d.%06d ",
186 				 /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/
187 				 loc_t.wHour, loc_t.wMinute, loc_t.wSecond, loc_t.wMilliseconds);
188 		time_buffer[sizeof(time_buffer) - 1 ] = '\0';
189 #else
190 		struct timeval tv;
191 		struct tm *tm_p;
192 		if (gettimeofday(&tv, NULL) != -1) {
193 			if ((tm_p= localtime((const time_t *)&tv.tv_sec))) {
194 				snprintf(time_buffer, sizeof(time_buffer) - 1,
195 						 /* "%04d-%02d-%02d " */
196 						 "%02d:%02d:%02d.%06d ",
197 						 /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/
198 						 tm_p->tm_hour, tm_p->tm_min, tm_p->tm_sec,
199 						 (int) (tv.tv_usec));
200 				time_buffer[sizeof(time_buffer) - 1 ] = '\0';
201 			}
202 		}
203 #endif
204 	}
205 	if (flags & MYSQLND_DEBUG_DUMP_FILE) {
206 		snprintf(file_buffer, sizeof(file_buffer) - 1, "%14s: ", file);
207 		file_buffer[sizeof(file_buffer) - 1 ] = '\0';
208 	}
209 	if (flags & MYSQLND_DEBUG_DUMP_LINE) {
210 		snprintf(line_buffer, sizeof(line_buffer) - 1, "%5u: ", line);
211 		line_buffer[sizeof(line_buffer) - 1 ] = '\0';
212 	}
213 	if (flags & MYSQLND_DEBUG_DUMP_LEVEL) {
214 		snprintf(level_buffer, sizeof(level_buffer) - 1, "%4u: ", level);
215 		level_buffer[sizeof(level_buffer) - 1 ] = '\0';
216 	}
217 
218 	va_start(args, format);
219 	mnd_vsprintf(&buffer, 0, format, args);
220 	va_end(args);
221 
222 	message_line_len = mnd_sprintf(&message_line, 0, "%s%s%s%s%s%s%s%s\n",
223 								flags & MYSQLND_DEBUG_DUMP_PID? pid_buffer:"",
224 								flags & MYSQLND_DEBUG_DUMP_TIME? time_buffer:"",
225 								flags & MYSQLND_DEBUG_DUMP_FILE? file_buffer:"",
226 								flags & MYSQLND_DEBUG_DUMP_LINE? line_buffer:"",
227 								flags & MYSQLND_DEBUG_DUMP_LEVEL? level_buffer:"",
228 								pipe_buffer, type? type:"", buffer);
229 	mnd_sprintf_free(buffer);
230 	ret = php_stream_write(self->stream, message_line, message_line_len)? PASS:FAIL;
231 	mnd_sprintf_free(message_line);
232 
233 	if (flags & MYSQLND_DEBUG_FLUSH) {
234 		self->m->close(self);
235 		self->m->open(self, TRUE);
236 	}
237 	return ret;
238 }
239 /* }}} */
240 
241 
242 /* FALSE - The DBG_ calls won't be traced, TRUE - will be traced */
243 /* {{{ mysqlnd_debug::func_enter */
244 static zend_bool
MYSQLND_METHOD(mysqlnd_debug,func_enter)245 MYSQLND_METHOD(mysqlnd_debug, func_enter)(MYSQLND_DEBUG * self,
246 										  unsigned int line, const char * const file,
247 										  const char * const func_name, unsigned int func_name_len)
248 {
249 	if ((self->flags & MYSQLND_DEBUG_DUMP_TRACE) == 0 || self->file_name == NULL) {
250 		return FALSE;
251 	}
252 	if ((uint32_t) zend_stack_count(&self->call_stack) >= self->nest_level_limit) {
253 		return FALSE;
254 	}
255 
256 	if ((self->flags & MYSQLND_DEBUG_TRACE_MEMORY_CALLS) == 0 && self->skip_functions) {
257 		const char ** p = self->skip_functions;
258 		while (*p) {
259 			if (*p == func_name) {
260 				zend_stack_push(&self->call_stack, &mysqlnd_debug_empty_string);
261 #ifndef MYSQLND_PROFILING_DISABLED
262 				if (self->flags & MYSQLND_DEBUG_PROFILE_CALLS) {
263 					uint64_t some_time = 0;
264 					zend_stack_push(&self->call_time_stack, &some_time);
265 				}
266 #endif
267 				return FALSE;
268 			}
269 			p++;
270 		}
271 	}
272 
273 	zend_stack_push(&self->call_stack, &func_name);
274 #ifndef MYSQLND_PROFILING_DISABLED
275 	if (self->flags & MYSQLND_DEBUG_PROFILE_CALLS) {
276 		uint64_t some_time = 0;
277 		zend_stack_push(&self->call_time_stack, &some_time);
278 	}
279 #endif
280 
281 	if (zend_hash_num_elements(&self->not_filtered_functions) &&
282 		0 == zend_hash_str_exists(&self->not_filtered_functions, func_name, strlen(func_name)))
283 	{
284 		return FALSE;
285 	}
286 
287 	self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, ">%s", func_name);
288 	return TRUE;
289 }
290 /* }}} */
291 
292 #ifndef MYSQLND_PROFILING_DISABLED
293 struct st_mysqlnd_dbg_function_profile {
294 	uint64_t calls;
295 	uint64_t min_own;
296 	uint64_t max_own;
297 	uint64_t avg_own;
298 	uint64_t own_underporm_calls;
299 	uint64_t min_in_calls;
300 	uint64_t max_in_calls;
301 	uint64_t avg_in_calls;
302 	uint64_t in_calls_underporm_calls;
303 	uint64_t min_total;
304 	uint64_t max_total;
305 	uint64_t avg_total;
306 	uint64_t total_underporm_calls;
307 };
308 #define PROFILE_UNDERPERFORM_THRESHOLD 10
309 #endif
310 
311 /* {{{ mysqlnd_debug::func_leave */
312 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,func_leave)313 MYSQLND_METHOD(mysqlnd_debug, func_leave)(MYSQLND_DEBUG * self, unsigned int line, const char * const file, uint64_t call_time)
314 {
315 	char **func_name;
316 	uint64_t * parent_non_own_time_ptr = NULL, * mine_non_own_time_ptr = NULL;
317 	uint64_t mine_non_own_time = 0;
318 	zend_bool profile_calls = self->flags & MYSQLND_DEBUG_PROFILE_CALLS? TRUE:FALSE;
319 
320 	if ((self->flags & MYSQLND_DEBUG_DUMP_TRACE) == 0 || self->file_name == NULL) {
321 		return PASS;
322 	}
323 	if ((uint32_t) zend_stack_count(&self->call_stack) >= self->nest_level_limit) {
324 		return PASS;
325 	}
326 
327 	func_name = zend_stack_top(&self->call_stack);
328 
329 #ifndef MYSQLND_PROFILING_DISABLED
330 	if (profile_calls) {
331 		mine_non_own_time_ptr = zend_stack_top(&self->call_time_stack);
332 		mine_non_own_time = *mine_non_own_time_ptr;
333 		zend_stack_del_top(&self->call_time_stack); /* callee - removing ourselves */
334 	}
335 #endif
336 
337 	if ((*func_name)[0] == '\0') {
338 		; /* don't log that function */
339 	} else if (!zend_hash_num_elements(&self->not_filtered_functions) ||
340 			   1 == zend_hash_str_exists(&self->not_filtered_functions, (*func_name), strlen((*func_name))))
341 	{
342 #ifndef MYSQLND_PROFILING_DISABLED
343 		if (FALSE == profile_calls) {
344 #endif
345 			self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, "<%s", *func_name);
346 
347 #ifndef MYSQLND_PROFILING_DISABLED
348 		} else {
349 			struct st_mysqlnd_dbg_function_profile f_profile_stack = {0};
350 			struct st_mysqlnd_dbg_function_profile * f_profile = NULL;
351 			uint64_t own_time = call_time - mine_non_own_time;
352 			uint32_t func_name_len = strlen(*func_name);
353 
354 			self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, "<%s (total=%u own=%u in_calls=%u)",
355 						*func_name, (unsigned int) call_time, (unsigned int) own_time, (unsigned int) mine_non_own_time
356 					);
357 
358 			if ((f_profile = zend_hash_str_find_ptr(&self->function_profiles, *func_name, func_name_len)) != NULL) {
359 				/* found */
360 					if (f_profile) {
361 					if (mine_non_own_time < f_profile->min_in_calls) {
362 						f_profile->min_in_calls = mine_non_own_time;
363 					} else if (mine_non_own_time > f_profile->max_in_calls) {
364 						f_profile->max_in_calls = mine_non_own_time;
365 					}
366 					f_profile->avg_in_calls = (f_profile->avg_in_calls * f_profile->calls + mine_non_own_time) / (f_profile->calls + 1);
367 
368 					if (own_time < f_profile->min_own) {
369 						f_profile->min_own = own_time;
370 					} else if (own_time > f_profile->max_own) {
371 						f_profile->max_own = own_time;
372 					}
373 					f_profile->avg_own = (f_profile->avg_own * f_profile->calls + own_time) / (f_profile->calls + 1);
374 
375 					if (call_time < f_profile->min_total) {
376 						f_profile->min_total = call_time;
377 					} else if (call_time > f_profile->max_total) {
378 						f_profile->max_total = call_time;
379 					}
380 					f_profile->avg_total = (f_profile->avg_total * f_profile->calls + call_time) / (f_profile->calls + 1);
381 
382 					++f_profile->calls;
383 					if (f_profile->calls > PROFILE_UNDERPERFORM_THRESHOLD) {
384 						if (f_profile->avg_in_calls < mine_non_own_time) {
385 							f_profile->in_calls_underporm_calls++;
386 						}
387 						if (f_profile->avg_own < own_time) {
388 							f_profile->own_underporm_calls++;
389 						}
390 						if (f_profile->avg_total < call_time) {
391 							f_profile->total_underporm_calls++;
392 						}
393 					}
394 				}
395 			} else {
396 				/* add */
397 				f_profile = &f_profile_stack;
398 				f_profile->min_in_calls = f_profile->max_in_calls = f_profile->avg_in_calls = mine_non_own_time;
399 				f_profile->min_total = f_profile->max_total = f_profile->avg_total = call_time;
400 				f_profile->min_own = f_profile->max_own = f_profile->avg_own = own_time;
401 				f_profile->calls = 1;
402 				zend_hash_str_add_mem(&self->function_profiles, *func_name, func_name_len, f_profile, sizeof(struct st_mysqlnd_dbg_function_profile));
403 			}
404 			if ((uint32_t) zend_stack_count(&self->call_time_stack)) {
405 				uint64_t parent_non_own_time = 0;
406 
407 				parent_non_own_time_ptr = zend_stack_top(&self->call_time_stack);
408 				parent_non_own_time = *parent_non_own_time_ptr;
409 				parent_non_own_time += call_time;
410 				zend_stack_del_top(&self->call_time_stack); /* the caller */
411 				zend_stack_push(&self->call_time_stack, &parent_non_own_time); /* add back the caller */
412 			}
413 		}
414 #endif
415 	}
416 
417 	return zend_stack_del_top(&self->call_stack) == SUCCESS? PASS:FAIL;
418 }
419 /* }}} */
420 
421 
422 /* {{{ mysqlnd_debug::close */
423 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,close)424 MYSQLND_METHOD(mysqlnd_debug, close)(MYSQLND_DEBUG * self)
425 {
426 	if (self->stream) {
427 #ifndef MYSQLND_PROFILING_DISABLED
428 		if (!(self->flags & MYSQLND_DEBUG_FLUSH) && (self->flags & MYSQLND_DEBUG_PROFILE_CALLS)) {
429 			struct st_mysqlnd_dbg_function_profile * f_profile;
430 			zend_string	*string_key = NULL;
431 
432 			self->m->log_va(self, __LINE__, __FILE__, 0, "info : ",
433 					"number of functions: %d", zend_hash_num_elements(&self->function_profiles));
434 			ZEND_HASH_FOREACH_STR_KEY_PTR(&self->function_profiles, string_key, f_profile) {
435 				self->m->log_va(self, __LINE__, __FILE__, -1, "info : ",
436 						"%-40s\tcalls=%5llu  own_slow=%5llu  in_calls_slow=%5llu  total_slow=%5llu"
437 						"   min_own=%5llu  max_own=%7llu  avg_own=%7llu   "
438 						"   min_in_calls=%5llu  max_in_calls=%7llu  avg_in_calls=%7llu"
439 						"   min_total=%5llu  max_total=%7llu  avg_total=%7llu"
440 						,ZSTR_VAL(string_key)
441 						,(uint64_t) f_profile->calls
442 						,(uint64_t) f_profile->own_underporm_calls
443 						,(uint64_t) f_profile->in_calls_underporm_calls
444 						,(uint64_t) f_profile->total_underporm_calls
445 
446 						,(uint64_t) f_profile->min_own
447 						,(uint64_t) f_profile->max_own
448 						,(uint64_t) f_profile->avg_own
449 						,(uint64_t) f_profile->min_in_calls
450 						,(uint64_t) f_profile->max_in_calls
451 						,(uint64_t) f_profile->avg_in_calls
452 						,(uint64_t) f_profile->min_total
453 						,(uint64_t) f_profile->max_total
454 						,(uint64_t) f_profile->avg_total
455 						);
456 			} ZEND_HASH_FOREACH_END();
457 		}
458 #endif
459 
460 		php_stream_close(self->stream);
461 		self->stream = NULL;
462 	}
463 	/* no DBG_RETURN please */
464 	return PASS;
465 }
466 /* }}} */
467 
468 
469 /* {{{ mysqlnd_res_meta::free */
470 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,free)471 MYSQLND_METHOD(mysqlnd_debug, free)(MYSQLND_DEBUG * self)
472 {
473 	if (self->file_name && self->file_name != mysqlnd_debug_default_trace_file) {
474 		efree(self->file_name);
475 		self->file_name = NULL;
476 	}
477 	zend_stack_destroy(&self->call_stack);
478 	zend_stack_destroy(&self->call_time_stack);
479 	zend_hash_destroy(&self->not_filtered_functions);
480 	zend_hash_destroy(&self->function_profiles);
481 	free(self);
482 	return PASS;
483 }
484 /* }}} */
485 
486 enum mysqlnd_debug_parser_state
487 {
488 	PARSER_WAIT_MODIFIER,
489 	PARSER_WAIT_COLON,
490 	PARSER_WAIT_VALUE
491 };
492 
493 
494 /* {{{ mysqlnd_res_meta::set_mode */
495 static void
MYSQLND_METHOD(mysqlnd_debug,set_mode)496 MYSQLND_METHOD(mysqlnd_debug, set_mode)(MYSQLND_DEBUG * self, const char * const mode)
497 {
498 	unsigned int mode_len, i;
499 	enum mysqlnd_debug_parser_state state = PARSER_WAIT_MODIFIER;
500 
501 	mode_len = mode? strlen(mode) : 0;
502 
503 	self->flags = 0;
504 	self->nest_level_limit = 0;
505 	if (self->file_name && self->file_name != mysqlnd_debug_default_trace_file) {
506 		efree(self->file_name);
507 		self->file_name = NULL;
508 	}
509 	if (zend_hash_num_elements(&self->not_filtered_functions)) {
510 		zend_hash_destroy(&self->not_filtered_functions);
511 		zend_hash_init(&self->not_filtered_functions, 0, NULL, NULL, 0);
512 	}
513 
514 	for (i = 0; i < mode_len; i++) {
515 		switch (mode[i]) {
516 			case 'O':
517 			case 'A':
518 				self->flags |= MYSQLND_DEBUG_FLUSH;
519 			case 'a':
520 			case 'o':
521 				if (mode[i] == 'a' || mode[i] == 'A') {
522 					self->flags |= MYSQLND_DEBUG_APPEND;
523 				}
524 				if (i + 1 < mode_len && mode[i+1] == ',') {
525 					unsigned int j = i + 2;
526 #ifdef PHP_WIN32
527 					if (i+4 < mode_len && mode[i+3] == ':' && (mode[i+4] == '\\' || mode[i+4] == '/')) {
528 						j = i + 5;
529 					}
530 #endif
531 					while (j < mode_len) {
532 						if (mode[j] == ':') {
533 							break;
534 						}
535 						j++;
536 					}
537 					if (j > i + 2) {
538 						self->file_name = estrndup(mode + i + 2, j - i - 2);
539 					}
540 					i = j;
541 				} else {
542 					if (!self->file_name)
543 						self->file_name = (char *) mysqlnd_debug_default_trace_file;
544 				}
545 				state = PARSER_WAIT_COLON;
546 				break;
547 			case ':':
548 #if 0
549 				if (state != PARSER_WAIT_COLON) {
550 					php_error_docref(NULL, E_WARNING, "Consecutive semicolons at position %u", i);
551 				}
552 #endif
553 				state = PARSER_WAIT_MODIFIER;
554 				break;
555 			case 'f': /* limit output to these functions */
556 				if (i + 1 < mode_len && mode[i+1] == ',') {
557 					unsigned int j = i + 2;
558 					i++;
559 					while (j < mode_len) {
560 						if (mode[j] == ':') {
561 							/* function names with :: */
562 							if ((j + 1 < mode_len) && mode[j+1] == ':') {
563 								j += 2;
564 								continue;
565 							}
566 						}
567 						if (mode[j] == ',' || mode[j] == ':') {
568 							if (j > i + 2) {
569 								char func_name[1024];
570 								unsigned int func_name_len = MIN(sizeof(func_name) - 1, j - i - 1);
571 								memcpy(func_name, mode + i + 1, func_name_len);
572 								func_name[func_name_len] = '\0';
573 
574 								zend_hash_str_add_empty_element(&self->not_filtered_functions,
575 															func_name, func_name_len);
576 								i = j;
577 							}
578 							if (mode[j] == ':') {
579 								break;
580 							}
581 						}
582 						j++;
583 					}
584 					i = j;
585 				} else {
586 #if 0
587 					php_error_docref(NULL, E_WARNING,
588 									 "Expected list of functions for '%c' found none", mode[i]);
589 #endif
590 				}
591 				state = PARSER_WAIT_COLON;
592 				break;
593 			case 'D':
594 			case 'd':
595 			case 'g':
596 			case 'p':
597 				/* unsupported */
598 				if ((i + 1) < mode_len && mode[i+1] == ',') {
599 					i+= 2;
600 					while (i < mode_len) {
601 						if (mode[i] == ':') {
602 							break;
603 						}
604 						i++;
605 					}
606 				}
607 				state = PARSER_WAIT_COLON;
608 				break;
609 			case 'F':
610 				self->flags |= MYSQLND_DEBUG_DUMP_FILE;
611 				state = PARSER_WAIT_COLON;
612 				break;
613 			case 'i':
614 				self->flags |= MYSQLND_DEBUG_DUMP_PID;
615 				state = PARSER_WAIT_COLON;
616 				break;
617 			case 'L':
618 				self->flags |= MYSQLND_DEBUG_DUMP_LINE;
619 				state = PARSER_WAIT_COLON;
620 				break;
621 			case 'n':
622 				self->flags |= MYSQLND_DEBUG_DUMP_LEVEL;
623 				state = PARSER_WAIT_COLON;
624 				break;
625 			case 't':
626 				if (mode[i+1] == ',') {
627 					unsigned int j = i + 2;
628 					while (j < mode_len) {
629 						if (mode[j] == ':') {
630 							break;
631 						}
632 						j++;
633 					}
634 					if (j > i + 2) {
635 						char *value_str = estrndup(mode + i + 2, j - i - 2);
636 						self->nest_level_limit = atoi(value_str);
637 						efree(value_str);
638 					}
639 					i = j;
640 				} else {
641 					self->nest_level_limit = 200; /* default value for FF DBUG */
642 				}
643 				self->flags |= MYSQLND_DEBUG_DUMP_TRACE;
644 				state = PARSER_WAIT_COLON;
645 				break;
646 			case 'T':
647 				self->flags |= MYSQLND_DEBUG_DUMP_TIME;
648 				state = PARSER_WAIT_COLON;
649 				break;
650 			case 'N':
651 			case 'P':
652 			case 'r':
653 			case 'S':
654 				state = PARSER_WAIT_COLON;
655 				break;
656 			case 'm': /* mysqlnd extension - trace memory functions */
657 				self->flags |= MYSQLND_DEBUG_TRACE_MEMORY_CALLS;
658 				state = PARSER_WAIT_COLON;
659 				break;
660 			case 'x': /* mysqlnd extension - profile calls */
661 				self->flags |= MYSQLND_DEBUG_PROFILE_CALLS;
662 				state = PARSER_WAIT_COLON;
663 				break;
664 			default:
665 				if (state == PARSER_WAIT_MODIFIER) {
666 #if 0
667 					php_error_docref(NULL, E_WARNING, "Unrecognized format '%c'", mode[i]);
668 #endif
669 					if (i+1 < mode_len && mode[i+1] == ',') {
670 						i+= 2;
671 						while (i < mode_len) {
672 							if (mode[i] == ':') {
673 								break;
674 							}
675 							i++;
676 						}
677 					}
678 					state = PARSER_WAIT_COLON;
679 				} else if (state == PARSER_WAIT_COLON) {
680 #if 0
681 					php_error_docref(NULL, E_WARNING, "Colon expected, '%c' found", mode[i]);
682 #endif
683 				}
684 				break;
685 		}
686 	}
687 }
688 /* }}} */
689 
690 MYSQLND_CLASS_METHODS_START(mysqlnd_debug)
691 	MYSQLND_METHOD(mysqlnd_debug, open),
692 	MYSQLND_METHOD(mysqlnd_debug, set_mode),
693 	MYSQLND_METHOD(mysqlnd_debug, log),
694 	MYSQLND_METHOD(mysqlnd_debug, log_va),
695 	MYSQLND_METHOD(mysqlnd_debug, func_enter),
696 	MYSQLND_METHOD(mysqlnd_debug, func_leave),
697 	MYSQLND_METHOD(mysqlnd_debug, close),
698 	MYSQLND_METHOD(mysqlnd_debug, free),
699 MYSQLND_CLASS_METHODS_END;
700 
701 
free_ptr(zval * zv)702 static void free_ptr(zval *zv) {
703 	efree(Z_PTR_P(zv));
704 }
705 
706 /* {{{ mysqlnd_debug_init */
707 PHPAPI MYSQLND_DEBUG *
mysqlnd_debug_init(const char * skip_functions[])708 mysqlnd_debug_init(const char * skip_functions[])
709 {
710 	MYSQLND_DEBUG *ret = calloc(1, sizeof(MYSQLND_DEBUG));
711 
712 	ret->nest_level_limit = 0;
713 	ret->pid = getpid();
714 	zend_stack_init(&ret->call_stack, sizeof(char *));
715 	zend_stack_init(&ret->call_time_stack, sizeof(uint64_t));
716 	zend_hash_init(&ret->not_filtered_functions, 0, NULL, NULL, 0);
717 	zend_hash_init(&ret->function_profiles, 0, NULL, free_ptr, 0);
718 
719 	ret->m = & mysqlnd_mysqlnd_debug_methods;
720 	ret->skip_functions = skip_functions;
721 
722 	return ret;
723 }
724 /* }}} */
725 
726 
727 /* {{{ mysqlnd_debug */
mysqlnd_debug(const char * mode)728 PHPAPI void mysqlnd_debug(const char * mode)
729 {
730 #if PHP_DEBUG
731 	MYSQLND_DEBUG * dbg = MYSQLND_G(dbg);
732 	if (!dbg) {
733 		struct st_mysqlnd_plugin_trace_log * trace_log_plugin = mysqlnd_plugin_find("debug_trace");
734 		if (trace_log_plugin) {
735 			dbg = trace_log_plugin->methods.trace_instance_init(mysqlnd_debug_std_no_trace_funcs);
736 			if (!dbg) {
737 				return;
738 			}
739 			MYSQLND_G(dbg) = dbg;
740 		}
741 	}
742 	if (dbg) {
743 		dbg->m->close(dbg);
744 		dbg->m->set_mode(dbg, mode);
745 		while (zend_stack_count(&dbg->call_stack)) {
746 			zend_stack_del_top(&dbg->call_stack);
747 		}
748 		while (zend_stack_count(&dbg->call_time_stack)) {
749 			zend_stack_del_top(&dbg->call_time_stack);
750 		}
751 	}
752 #endif
753 }
754 /* }}} */
755 
756 
757 static struct st_mysqlnd_plugin_trace_log mysqlnd_plugin_trace_log_plugin =
758 {
759 	{
760 		MYSQLND_PLUGIN_API_VERSION,
761 		"debug_trace",
762 		MYSQLND_VERSION_ID,
763 		PHP_MYSQLND_VERSION,
764 		"PHP License 3.01",
765 		"Andrey Hristov <andrey@php.net>,  Ulf Wendel <uw@php.net>, Georg Richter <georg@php.net>",
766 		{
767 			NULL, /* no statistics , will be filled later if there are some */
768 			NULL, /* no statistics */
769 		},
770 		{
771 			NULL /* plugin shutdown */
772 		}
773 	},
774 	{/* methods */
775 		mysqlnd_debug_init,
776 	}
777 };
778 
779 
780 /* {{{ mysqlnd_debug_trace_plugin_register */
781 void
mysqlnd_debug_trace_plugin_register(void)782 mysqlnd_debug_trace_plugin_register(void)
783 {
784 	mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_plugin_trace_log_plugin);
785 }
786 /* }}} */
787