xref: /PHP-5.3/ext/mysqlnd/mysqlnd_debug.c (revision bc11e6fd)
1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 2006-2013 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: Georg Richter <georg@mysql.com>                             |
16   |          Andrey Hristov <andrey@mysql.com>                           |
17   |          Ulf Wendel <uwendel@mysql.com>                              |
18   +----------------------------------------------------------------------+
19 */
20 
21 /* $Id$ */
22 
23 #include "php.h"
24 #include "mysqlnd.h"
25 #include "mysqlnd_priv.h"
26 #include "mysqlnd_debug.h"
27 #include "mysqlnd_wireprotocol.h"
28 #include "mysqlnd_statistics.h"
29 #include "zend_builtin_functions.h"
30 
31 static const char * const mysqlnd_debug_default_trace_file = "/tmp/mysqlnd.trace";
32 
33 #ifdef ZTS
34 #define MYSQLND_ZTS(self) TSRMLS_D = (self)->TSRMLS_C
35 #else
36 #define MYSQLND_ZTS(self)
37 #endif
38 
39 static const char mysqlnd_emalloc_name[]	= "_mysqlnd_emalloc";
40 static const char mysqlnd_pemalloc_name[]	= "_mysqlnd_pemalloc";
41 static const char mysqlnd_ecalloc_name[]	= "_mysqlnd_ecalloc";
42 static const char mysqlnd_pecalloc_name[]	= "_mysqlnd_pecalloc";
43 static const char mysqlnd_erealloc_name[]	= "_mysqlnd_erealloc";
44 static const char mysqlnd_perealloc_name[]	= "_mysqlnd_perealloc";
45 static const char mysqlnd_efree_name[]		= "_mysqlnd_efree";
46 static const char mysqlnd_pefree_name[]		= "_mysqlnd_pefree";
47 static const char mysqlnd_malloc_name[]		= "_mysqlnd_malloc";
48 static const char mysqlnd_calloc_name[]		= "_mysqlnd_calloc";
49 static const char mysqlnd_realloc_name[]	= "_mysqlnd_realloc";
50 static const char mysqlnd_free_name[]		= "_mysqlnd_free";
51 static const char mysqlnd_pestrndup_name[]	= "_mysqlnd_pestrndup";
52 static const char mysqlnd_pestrdup_name[]	= "_mysqlnd_pestrdup";
53 
54 const char * mysqlnd_debug_std_no_trace_funcs[] =
55 {
56 	mysqlnd_emalloc_name,
57 	mysqlnd_ecalloc_name,
58 	mysqlnd_efree_name,
59 	mysqlnd_erealloc_name,
60 	mysqlnd_pemalloc_name,
61 	mysqlnd_pecalloc_name,
62 	mysqlnd_pefree_name,
63 	mysqlnd_perealloc_name,
64 	mysqlnd_malloc_name,
65 	mysqlnd_calloc_name,
66 	mysqlnd_realloc_name,
67 	mysqlnd_free_name,
68 	mysqlnd_pestrndup_name,
69 	mysqlnd_read_header_name,
70 	mysqlnd_read_body_name,
71 	NULL /* must be always last */
72 };
73 
74 
75 /* {{{ mysqlnd_debug::open */
76 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,open)77 MYSQLND_METHOD(mysqlnd_debug, open)(MYSQLND_DEBUG * self, zend_bool reopen)
78 {
79 	MYSQLND_ZTS(self);
80 
81 	if (!self->file_name) {
82 		return FAIL;
83 	}
84 
85 	self->stream = php_stream_open_wrapper(self->file_name,
86 										   reopen == TRUE || self->flags & MYSQLND_DEBUG_APPEND? "ab":"wb",
87 										   REPORT_ERRORS, NULL);
88 	return self->stream? PASS:FAIL;
89 }
90 /* }}} */
91 
92 
93 /* {{{ mysqlnd_debug::log */
94 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,log)95 MYSQLND_METHOD(mysqlnd_debug, log)(MYSQLND_DEBUG * self,
96 								   unsigned int line, const char * const file,
97 								   unsigned int level, const char * type, const char * message)
98 {
99 	char pipe_buffer[512];
100 	enum_func_status ret;
101 	int i;
102 	char * message_line;
103 	unsigned int message_line_len;
104 	unsigned int flags = self->flags;
105 	char pid_buffer[10], time_buffer[30], file_buffer[200],
106 		 line_buffer[6], level_buffer[7];
107 	MYSQLND_ZTS(self);
108 
109 	if (!self->stream && FAIL == self->m->open(self, FALSE)) {
110 		return FAIL;
111 	}
112 
113 	if (level == -1) {
114 		level = zend_stack_count(&self->call_stack);
115 	}
116 	i = MIN(level, sizeof(pipe_buffer) / 2  - 1);
117 	pipe_buffer[i*2] = '\0';
118 	for (;i > 0;i--) {
119 		pipe_buffer[i*2 - 1] = ' ';
120 		pipe_buffer[i*2 - 2] = '|';
121 	}
122 
123 
124 	if (flags & MYSQLND_DEBUG_DUMP_PID) {
125 		snprintf(pid_buffer, sizeof(pid_buffer) - 1, "%5u: ", self->pid);
126 		pid_buffer[sizeof(pid_buffer) - 1 ] = '\0';
127 	}
128 	if (flags & MYSQLND_DEBUG_DUMP_TIME) {
129 		/* The following from FF's DBUG library, which is in the public domain */
130 #if defined(PHP_WIN32)
131 		/* FIXME This doesn't give microseconds as in Unix case, and the resolution is
132 		in system ticks, 10 ms intervals. See my_getsystime.c for high res */
133 		SYSTEMTIME loc_t;
134 		GetLocalTime(&loc_t);
135 		snprintf(time_buffer, sizeof(time_buffer) - 1,
136 				 /* "%04d-%02d-%02d " */
137 				 "%02d:%02d:%02d.%06d ",
138 				 /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/
139 				 loc_t.wHour, loc_t.wMinute, loc_t.wSecond, loc_t.wMilliseconds);
140 		time_buffer[sizeof(time_buffer) - 1 ] = '\0';
141 #else
142 		struct timeval tv;
143 		struct tm *tm_p;
144 		if (gettimeofday(&tv, NULL) != -1) {
145 			if ((tm_p= localtime((const time_t *)&tv.tv_sec))) {
146 				snprintf(time_buffer, sizeof(time_buffer) - 1,
147 						 /* "%04d-%02d-%02d " */
148 						 "%02d:%02d:%02d.%06d ",
149 						 /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/
150 						 tm_p->tm_hour, tm_p->tm_min, tm_p->tm_sec,
151 						 (int) (tv.tv_usec));
152 				time_buffer[sizeof(time_buffer) - 1 ] = '\0';
153 			}
154 		}
155 #endif
156 	}
157 	if (flags & MYSQLND_DEBUG_DUMP_FILE) {
158 		snprintf(file_buffer, sizeof(file_buffer) - 1, "%14s: ", file);
159 		file_buffer[sizeof(file_buffer) - 1 ] = '\0';
160 	}
161 	if (flags & MYSQLND_DEBUG_DUMP_LINE) {
162 		snprintf(line_buffer, sizeof(line_buffer) - 1, "%5u: ", line);
163 		line_buffer[sizeof(line_buffer) - 1 ] = '\0';
164 	}
165 	if (flags & MYSQLND_DEBUG_DUMP_LEVEL) {
166 		snprintf(level_buffer, sizeof(level_buffer) - 1, "%4u: ", level);
167 		level_buffer[sizeof(level_buffer) - 1 ] = '\0';
168 	}
169 
170 	message_line_len = spprintf(&message_line, 0, "%s%s%s%s%s%s%s%s\n",
171 								flags & MYSQLND_DEBUG_DUMP_PID? pid_buffer:"",
172 								flags & MYSQLND_DEBUG_DUMP_TIME? time_buffer:"",
173 								flags & MYSQLND_DEBUG_DUMP_FILE? file_buffer:"",
174 								flags & MYSQLND_DEBUG_DUMP_LINE? line_buffer:"",
175 								flags & MYSQLND_DEBUG_DUMP_LEVEL? level_buffer:"",
176 								pipe_buffer, type? type:"", message);
177 
178 	ret = php_stream_write(self->stream, message_line, message_line_len)? PASS:FAIL;
179 	efree(message_line); /* allocated by spprintf */
180 	if (flags & MYSQLND_DEBUG_FLUSH) {
181 		self->m->close(self);
182 		self->m->open(self, TRUE);
183 	}
184 	return ret;
185 }
186 /* }}} */
187 
188 
189 /* {{{ mysqlnd_debug::log_va */
190 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,log_va)191 MYSQLND_METHOD(mysqlnd_debug, log_va)(MYSQLND_DEBUG *self,
192 									  unsigned int line, const char * const file,
193 									  unsigned int level, const char * type,
194 									  const char *format, ...)
195 {
196 	char pipe_buffer[512];
197 	int i;
198 	enum_func_status ret;
199 	char * message_line, *buffer;
200 	unsigned int message_line_len;
201 	va_list args;
202 	unsigned int flags = self->flags;
203 	char pid_buffer[10], time_buffer[30], file_buffer[200],
204 		 line_buffer[6], level_buffer[7];
205 	MYSQLND_ZTS(self);
206 
207 	if (!self->stream && FAIL == self->m->open(self, FALSE)) {
208 		return FAIL;
209 	}
210 
211 	if (level == -1) {
212 		level = zend_stack_count(&self->call_stack);
213 	}
214 	i = MIN(level, sizeof(pipe_buffer) / 2  - 1);
215 	pipe_buffer[i*2] = '\0';
216 	for (;i > 0;i--) {
217 		pipe_buffer[i*2 - 1] = ' ';
218 		pipe_buffer[i*2 - 2] = '|';
219 	}
220 
221 
222 	if (flags & MYSQLND_DEBUG_DUMP_PID) {
223 		snprintf(pid_buffer, sizeof(pid_buffer) - 1, "%5u: ", self->pid);
224 		pid_buffer[sizeof(pid_buffer) - 1 ] = '\0';
225 	}
226 	if (flags & MYSQLND_DEBUG_DUMP_TIME) {
227 		/* The following from FF's DBUG library, which is in the public domain */
228 #if defined(PHP_WIN32)
229 		/* FIXME This doesn't give microseconds as in Unix case, and the resolution is
230 		in system ticks, 10 ms intervals. See my_getsystime.c for high res */
231 		SYSTEMTIME loc_t;
232 		GetLocalTime(&loc_t);
233 		snprintf(time_buffer, sizeof(time_buffer) - 1,
234 				 /* "%04d-%02d-%02d " */
235 				 "%02d:%02d:%02d.%06d ",
236 				 /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/
237 				 loc_t.wHour, loc_t.wMinute, loc_t.wSecond, loc_t.wMilliseconds);
238 		time_buffer[sizeof(time_buffer) - 1 ] = '\0';
239 #else
240 		struct timeval tv;
241 		struct tm *tm_p;
242 		if (gettimeofday(&tv, NULL) != -1) {
243 			if ((tm_p= localtime((const time_t *)&tv.tv_sec))) {
244 				snprintf(time_buffer, sizeof(time_buffer) - 1,
245 						 /* "%04d-%02d-%02d " */
246 						 "%02d:%02d:%02d.%06d ",
247 						 /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/
248 						 tm_p->tm_hour, tm_p->tm_min, tm_p->tm_sec,
249 						 (int) (tv.tv_usec));
250 				time_buffer[sizeof(time_buffer) - 1 ] = '\0';
251 			}
252 		}
253 #endif
254 	}
255 	if (flags & MYSQLND_DEBUG_DUMP_FILE) {
256 		snprintf(file_buffer, sizeof(file_buffer) - 1, "%14s: ", file);
257 		file_buffer[sizeof(file_buffer) - 1 ] = '\0';
258 	}
259 	if (flags & MYSQLND_DEBUG_DUMP_LINE) {
260 		snprintf(line_buffer, sizeof(line_buffer) - 1, "%5u: ", line);
261 		line_buffer[sizeof(line_buffer) - 1 ] = '\0';
262 	}
263 	if (flags & MYSQLND_DEBUG_DUMP_LEVEL) {
264 		snprintf(level_buffer, sizeof(level_buffer) - 1, "%4u: ", level);
265 		level_buffer[sizeof(level_buffer) - 1 ] = '\0';
266 	}
267 
268 
269 	va_start(args, format);
270 	vspprintf(&buffer, 0, format, args);
271 	va_end(args);
272 
273 	message_line_len = spprintf(&message_line, 0, "%s%s%s%s%s%s%s%s\n",
274 								flags & MYSQLND_DEBUG_DUMP_PID? pid_buffer:"",
275 								flags & MYSQLND_DEBUG_DUMP_TIME? time_buffer:"",
276 								flags & MYSQLND_DEBUG_DUMP_FILE? file_buffer:"",
277 								flags & MYSQLND_DEBUG_DUMP_LINE? line_buffer:"",
278 								flags & MYSQLND_DEBUG_DUMP_LEVEL? level_buffer:"",
279 								pipe_buffer, type? type:"", buffer);
280 	efree(buffer);
281 	ret = php_stream_write(self->stream, message_line, message_line_len)? PASS:FAIL;
282 	efree(message_line); /* allocated by spprintf */
283 
284 	if (flags & MYSQLND_DEBUG_FLUSH) {
285 		self->m->close(self);
286 		self->m->open(self, TRUE);
287 	}
288 	return ret;
289 }
290 /* }}} */
291 
292 
293 /* FALSE - The DBG_ calls won't be traced, TRUE - will be traced */
294 /* {{{ mysqlnd_debug::func_enter */
295 static zend_bool
MYSQLND_METHOD(mysqlnd_debug,func_enter)296 MYSQLND_METHOD(mysqlnd_debug, func_enter)(MYSQLND_DEBUG * self,
297 										  unsigned int line, const char * const file,
298 										  const char * const func_name, unsigned int func_name_len)
299 {
300 	if ((self->flags & MYSQLND_DEBUG_DUMP_TRACE) == 0 || self->file_name == NULL) {
301 		return FALSE;
302 	}
303 	if ((uint) zend_stack_count(&self->call_stack) >= self->nest_level_limit) {
304 		return FALSE;
305 	}
306 
307 	if ((self->flags & MYSQLND_DEBUG_TRACE_MEMORY_CALLS) == 0 && self->skip_functions) {
308 		const char ** p = self->skip_functions;
309 		while (*p) {
310 			if (*p == func_name) {
311 				zend_stack_push(&self->call_stack, "", sizeof(""));
312 #ifndef MYSQLND_PROFILING_DISABLED
313 				if (self->flags & MYSQLND_DEBUG_PROFILE_CALLS) {
314 					uint64_t some_time = 0;
315 					zend_stack_push(&self->call_time_stack, &some_time, sizeof(some_time));
316 				}
317 #endif
318 				return FALSE;
319 			}
320 			p++;
321 		}
322 	}
323 
324 	zend_stack_push(&self->call_stack, func_name, func_name_len + 1);
325 #ifndef MYSQLND_PROFILING_DISABLED
326 	if (self->flags & MYSQLND_DEBUG_PROFILE_CALLS) {
327 		uint64_t some_time = 0;
328 		zend_stack_push(&self->call_time_stack, &some_time, sizeof(some_time));
329 	}
330 #endif
331 
332 	if (zend_hash_num_elements(&self->not_filtered_functions) &&
333 		0 == zend_hash_exists(&self->not_filtered_functions, func_name, strlen(func_name) + 1))
334 	{
335 		return FALSE;
336 	}
337 
338 	self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, ">%s", func_name);
339 	return TRUE;
340 }
341 /* }}} */
342 
343 #ifndef MYSQLND_PROFILING_DISABLED
344 struct st_mysqlnd_dbg_function_profile {
345 	uint64_t calls;
346 	uint64_t min_own;
347 	uint64_t max_own;
348 	uint64_t avg_own;
349 	uint64_t own_underporm_calls;
350 	uint64_t min_in_calls;
351 	uint64_t max_in_calls;
352 	uint64_t avg_in_calls;
353 	uint64_t in_calls_underporm_calls;
354 	uint64_t min_total;
355 	uint64_t max_total;
356 	uint64_t avg_total;
357 	uint64_t total_underporm_calls;
358 };
359 #define PROFILE_UNDERPERFORM_THRESHOLD 10
360 #endif
361 
362 /* {{{ mysqlnd_debug::func_leave */
363 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,func_leave)364 MYSQLND_METHOD(mysqlnd_debug, func_leave)(MYSQLND_DEBUG * self, unsigned int line, const char * const file, uint64_t call_time)
365 {
366 	char *func_name;
367 	uint64_t * parent_non_own_time_ptr = NULL, * mine_non_own_time_ptr = NULL;
368 	uint64_t mine_non_own_time = 0;
369 	zend_bool profile_calls = self->flags & MYSQLND_DEBUG_PROFILE_CALLS? TRUE:FALSE;
370 
371 	if ((self->flags & MYSQLND_DEBUG_DUMP_TRACE) == 0 || self->file_name == NULL) {
372 		return PASS;
373 	}
374 	if ((uint) zend_stack_count(&self->call_stack) >= self->nest_level_limit) {
375 		return PASS;
376 	}
377 
378 	zend_stack_top(&self->call_stack, (void **)&func_name);
379 
380 #ifndef MYSQLND_PROFILING_DISABLED
381 	if (profile_calls) {
382 		zend_stack_top(&self->call_time_stack, (void **)&mine_non_own_time_ptr);
383 		mine_non_own_time = *mine_non_own_time_ptr;
384 		zend_stack_del_top(&self->call_time_stack); /* callee - removing ourselves */
385 	}
386 #endif
387 
388 	if (func_name[0] == '\0') {
389 		; /* don't log that function */
390 	} else if (!zend_hash_num_elements(&self->not_filtered_functions) ||
391 			   1 == zend_hash_exists(&self->not_filtered_functions, func_name, strlen(func_name) + 1))
392 	{
393 #ifndef MYSQLND_PROFILING_DISABLED
394 		if (FALSE == profile_calls) {
395 #endif
396 			self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, "<%s", func_name);
397 
398 #ifndef MYSQLND_PROFILING_DISABLED
399 		} else {
400 			struct st_mysqlnd_dbg_function_profile f_profile_stack = {0};
401 			struct st_mysqlnd_dbg_function_profile * f_profile = NULL;
402 			uint64_t own_time = call_time - mine_non_own_time;
403 			uint func_name_len = strlen(func_name);
404 
405 			self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, "<%s (total=%u own=%u in_calls=%u)",
406 						func_name, (unsigned int) call_time, (unsigned int) own_time, (unsigned int) mine_non_own_time
407 					);
408 
409 			if (SUCCESS == zend_hash_find(&self->function_profiles, func_name, func_name_len + 1, (void **) &f_profile)) {
410 				/* found */
411 					if (f_profile) {
412 					if (mine_non_own_time < f_profile->min_in_calls) {
413 						f_profile->min_in_calls = mine_non_own_time;
414 					} else if (mine_non_own_time > f_profile->max_in_calls) {
415 						f_profile->max_in_calls = mine_non_own_time;
416 					}
417 					f_profile->avg_in_calls = (f_profile->avg_in_calls * f_profile->calls + mine_non_own_time) / (f_profile->calls + 1);
418 
419 					if (own_time < f_profile->min_own) {
420 						f_profile->min_own = own_time;
421 					} else if (own_time > f_profile->max_own) {
422 						f_profile->max_own = own_time;
423 					}
424 					f_profile->avg_own = (f_profile->avg_own * f_profile->calls + own_time) / (f_profile->calls + 1);
425 
426 					if (call_time < f_profile->min_total) {
427 						f_profile->min_total = call_time;
428 					} else if (call_time > f_profile->max_total) {
429 						f_profile->max_total = call_time;
430 					}
431 					f_profile->avg_total = (f_profile->avg_total * f_profile->calls + call_time) / (f_profile->calls + 1);
432 
433 					++f_profile->calls;
434 					if (f_profile->calls > PROFILE_UNDERPERFORM_THRESHOLD) {
435 						if (f_profile->avg_in_calls < mine_non_own_time) {
436 							f_profile->in_calls_underporm_calls++;
437 						}
438 						if (f_profile->avg_own < own_time) {
439 							f_profile->own_underporm_calls++;
440 						}
441 						if (f_profile->avg_total < call_time) {
442 							f_profile->total_underporm_calls++;
443 						}
444 					}
445 				}
446 			} else {
447 				/* add */
448 				f_profile = &f_profile_stack;
449 				f_profile->min_in_calls = f_profile->max_in_calls = f_profile->avg_in_calls = mine_non_own_time;
450 				f_profile->min_total = f_profile->max_total = f_profile->avg_total = call_time;
451 				f_profile->min_own = f_profile->max_own = f_profile->avg_own = own_time;
452 				f_profile->calls = 1;
453 				zend_hash_add(&self->function_profiles, func_name, func_name_len+1, f_profile, sizeof(struct st_mysqlnd_dbg_function_profile), NULL);
454 			}
455 			if ((uint) zend_stack_count(&self->call_time_stack)) {
456 				uint64_t parent_non_own_time = 0;
457 
458 				zend_stack_top(&self->call_time_stack, (void **)&parent_non_own_time_ptr);
459 				parent_non_own_time = *parent_non_own_time_ptr;
460 				parent_non_own_time += call_time;
461 				zend_stack_del_top(&self->call_time_stack); /* the caller */
462 				zend_stack_push(&self->call_time_stack, &parent_non_own_time, sizeof(parent_non_own_time)); /* add back the caller */
463 			}
464 		}
465 #endif
466 	}
467 
468 	return zend_stack_del_top(&self->call_stack) == SUCCESS? PASS:FAIL;
469 }
470 /* }}} */
471 
472 
473 /* {{{ mysqlnd_debug::close */
474 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,close)475 MYSQLND_METHOD(mysqlnd_debug, close)(MYSQLND_DEBUG * self)
476 {
477 	MYSQLND_ZTS(self);
478 	if (self->stream) {
479 #ifndef MYSQLND_PROFILING_DISABLED
480 		if (!(self->flags & MYSQLND_DEBUG_FLUSH) && (self->flags & MYSQLND_DEBUG_PROFILE_CALLS)) {
481 			struct st_mysqlnd_dbg_function_profile * f_profile;
482 			HashPosition pos_values;
483 
484 			self->m->log_va(self, __LINE__, __FILE__, 0, "info : ",
485 					"number of functions: %d", zend_hash_num_elements(&self->function_profiles));
486 			zend_hash_internal_pointer_reset_ex(&self->function_profiles, &pos_values);
487 			while (zend_hash_get_current_data_ex(&self->function_profiles, (void **) &f_profile, &pos_values) == SUCCESS) {
488 				char	*string_key = NULL;
489 				uint	string_key_len;
490 				ulong	num_key;
491 
492 				zend_hash_get_current_key_ex(&self->function_profiles, &string_key, &string_key_len, &num_key, 0, &pos_values);
493 
494 				self->m->log_va(self, __LINE__, __FILE__, -1, "info : ",
495 						"%-40s\tcalls=%5llu  own_slow=%5llu  in_calls_slow=%5llu  total_slow=%5llu"
496 						"   min_own=%5llu  max_own=%7llu  avg_own=%7llu   "
497 						"   min_in_calls=%5llu  max_in_calls=%7llu  avg_in_calls=%7llu"
498 						"   min_total=%5llu  max_total=%7llu  avg_total=%7llu"
499 						,string_key
500 						,(uint64_t) f_profile->calls
501 						,(uint64_t) f_profile->own_underporm_calls
502 						,(uint64_t) f_profile->in_calls_underporm_calls
503 						,(uint64_t) f_profile->total_underporm_calls
504 
505 						,(uint64_t) f_profile->min_own
506 						,(uint64_t) f_profile->max_own
507 						,(uint64_t) f_profile->avg_own
508 						,(uint64_t) f_profile->min_in_calls
509 						,(uint64_t) f_profile->max_in_calls
510 						,(uint64_t) f_profile->avg_in_calls
511 						,(uint64_t) f_profile->min_total
512 						,(uint64_t) f_profile->max_total
513 						,(uint64_t) f_profile->avg_total
514 						);
515 				zend_hash_move_forward_ex(&self->function_profiles, &pos_values);
516 			}
517 		}
518 #endif
519 
520 		php_stream_free(self->stream, PHP_STREAM_FREE_CLOSE);
521 		self->stream = NULL;
522 	}
523 	/* no DBG_RETURN please */
524 	return PASS;
525 }
526 /* }}} */
527 
528 
529 /* {{{ mysqlnd_res_meta::free */
530 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,free)531 MYSQLND_METHOD(mysqlnd_debug, free)(MYSQLND_DEBUG * self)
532 {
533 	if (self->file_name && self->file_name != mysqlnd_debug_default_trace_file) {
534 		efree(self->file_name);
535 		self->file_name = NULL;
536 	}
537 	zend_stack_destroy(&self->call_stack);
538 	zend_stack_destroy(&self->call_time_stack);
539 	zend_hash_destroy(&self->not_filtered_functions);
540 	zend_hash_destroy(&self->function_profiles);
541 	efree(self);
542 	return PASS;
543 }
544 /* }}} */
545 
546 enum mysqlnd_debug_parser_state
547 {
548 	PARSER_WAIT_MODIFIER,
549 	PARSER_WAIT_COLON,
550 	PARSER_WAIT_VALUE
551 };
552 
553 
554 /* {{{ mysqlnd_res_meta::set_mode */
555 static void
MYSQLND_METHOD(mysqlnd_debug,set_mode)556 MYSQLND_METHOD(mysqlnd_debug, set_mode)(MYSQLND_DEBUG * self, const char * const mode)
557 {
558 	unsigned int mode_len = strlen(mode), i;
559 	enum mysqlnd_debug_parser_state state = PARSER_WAIT_MODIFIER;
560 
561 	self->flags = 0;
562 	self->nest_level_limit = 0;
563 	if (self->file_name && self->file_name != mysqlnd_debug_default_trace_file) {
564 		efree(self->file_name);
565 		self->file_name = NULL;
566 	}
567 	if (zend_hash_num_elements(&self->not_filtered_functions)) {
568 		zend_hash_destroy(&self->not_filtered_functions);
569 		zend_hash_init(&self->not_filtered_functions, 0, NULL, NULL, 0);
570 	}
571 
572 	for (i = 0; i < mode_len; i++) {
573 		switch (mode[i]) {
574 			case 'O':
575 			case 'A':
576 				self->flags |= MYSQLND_DEBUG_FLUSH;
577 			case 'a':
578 			case 'o':
579 				if (mode[i] == 'a' || mode[i] == 'A') {
580 					self->flags |= MYSQLND_DEBUG_APPEND;
581 				}
582 				if (i + 1 < mode_len && mode[i+1] == ',') {
583 					unsigned int j = i + 2;
584 #ifdef PHP_WIN32
585 					if (i+4 < mode_len && mode[i+3] == ':' && (mode[i+4] == '\\' || mode[i+5] == '/')) {
586 						j = i + 5;
587 					}
588 #endif
589 					while (j < mode_len) {
590 						if (mode[j] == ':') {
591 							break;
592 						}
593 						j++;
594 					}
595 					if (j > i + 2) {
596 						self->file_name = estrndup(mode + i + 2, j - i - 2);
597 					}
598 					i = j;
599 				} else {
600 					if (!self->file_name)
601 						self->file_name = (char *) mysqlnd_debug_default_trace_file;
602 				}
603 				state = PARSER_WAIT_COLON;
604 				break;
605 			case ':':
606 #if 0
607 				if (state != PARSER_WAIT_COLON) {
608 					php_error_docref(NULL TSRMLS_CC, E_WARNING, "Consecutive semicolons at position %u", i);
609 				}
610 #endif
611 				state = PARSER_WAIT_MODIFIER;
612 				break;
613 			case 'f': /* limit output to these functions */
614 				if (i + 1 < mode_len && mode[i+1] == ',') {
615 					unsigned int j = i + 2;
616 					i++;
617 					while (j < mode_len) {
618 						if (mode[j] == ':') {
619 							/* function names with :: */
620 							if ((j + 1 < mode_len) && mode[j+1] == ':') {
621 								j += 2;
622 								continue;
623 							}
624 						}
625 						if (mode[j] == ',' || mode[j] == ':') {
626 							if (j > i + 2) {
627 								char func_name[1024];
628 								unsigned int func_name_len = MIN(sizeof(func_name) - 1, j - i - 1);
629 								memcpy(func_name, mode + i + 1, func_name_len);
630 								func_name[func_name_len] = '\0';
631 
632 								zend_hash_add_empty_element(&self->not_filtered_functions,
633 															func_name, func_name_len + 1);
634 								i = j;
635 							}
636 							if (mode[j] == ':') {
637 								break;
638 							}
639 						}
640 						j++;
641 					}
642 					i = j;
643 				} else {
644 #if 0
645 					php_error_docref(NULL TSRMLS_CC, E_WARNING,
646 									 "Expected list of functions for '%c' found none", mode[i]);
647 #endif
648 				}
649 				state = PARSER_WAIT_COLON;
650 				break;
651 			case 'D':
652 			case 'd':
653 			case 'g':
654 			case 'p':
655 				/* unsupported */
656 				if ((i + 1) < mode_len && mode[i+1] == ',') {
657 					i+= 2;
658 					while (i < mode_len) {
659 						if (mode[i] == ':') {
660 							break;
661 						}
662 						i++;
663 					}
664 				}
665 				state = PARSER_WAIT_COLON;
666 				break;
667 			case 'F':
668 				self->flags |= MYSQLND_DEBUG_DUMP_FILE;
669 				state = PARSER_WAIT_COLON;
670 				break;
671 			case 'i':
672 				self->flags |= MYSQLND_DEBUG_DUMP_PID;
673 				state = PARSER_WAIT_COLON;
674 				break;
675 			case 'L':
676 				self->flags |= MYSQLND_DEBUG_DUMP_LINE;
677 				state = PARSER_WAIT_COLON;
678 				break;
679 			case 'n':
680 				self->flags |= MYSQLND_DEBUG_DUMP_LEVEL;
681 				state = PARSER_WAIT_COLON;
682 				break;
683 			case 't':
684 				if (mode[i+1] == ',') {
685 					unsigned int j = i + 2;
686 					while (j < mode_len) {
687 						if (mode[j] == ':') {
688 							break;
689 						}
690 						j++;
691 					}
692 					if (j > i + 2) {
693 						char *value_str = estrndup(mode + i + 2, j - i - 2);
694 						self->nest_level_limit = atoi(value_str);
695 						efree(value_str);
696 					}
697 					i = j;
698 				} else {
699 					self->nest_level_limit = 200; /* default value for FF DBUG */
700 				}
701 				self->flags |= MYSQLND_DEBUG_DUMP_TRACE;
702 				state = PARSER_WAIT_COLON;
703 				break;
704 			case 'T':
705 				self->flags |= MYSQLND_DEBUG_DUMP_TIME;
706 				state = PARSER_WAIT_COLON;
707 				break;
708 			case 'N':
709 			case 'P':
710 			case 'r':
711 			case 'S':
712 				state = PARSER_WAIT_COLON;
713 				break;
714 			case 'm': /* mysqlnd extension - trace memory functions */
715 				self->flags |= MYSQLND_DEBUG_TRACE_MEMORY_CALLS;
716 				state = PARSER_WAIT_COLON;
717 				break;
718 			case 'x': /* mysqlnd extension - profile calls */
719 				self->flags |= MYSQLND_DEBUG_PROFILE_CALLS;
720 				state = PARSER_WAIT_COLON;
721 				break;
722 			default:
723 				if (state == PARSER_WAIT_MODIFIER) {
724 #if 0
725 					php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unrecognized format '%c'", mode[i]);
726 #endif
727 					if (i+1 < mode_len && mode[i+1] == ',') {
728 						i+= 2;
729 						while (i < mode_len) {
730 							if (mode[i] == ':') {
731 								break;
732 							}
733 							i++;
734 						}
735 					}
736 					state = PARSER_WAIT_COLON;
737 				} else if (state == PARSER_WAIT_COLON) {
738 #if 0
739 					php_error_docref(NULL TSRMLS_CC, E_WARNING, "Colon expected, '%c' found", mode[i]);
740 #endif
741 				}
742 				break;
743 		}
744 	}
745 }
746 /* }}} */
747 
748 MYSQLND_CLASS_METHODS_START(mysqlnd_debug)
749 	MYSQLND_METHOD(mysqlnd_debug, open),
750 	MYSQLND_METHOD(mysqlnd_debug, set_mode),
751 	MYSQLND_METHOD(mysqlnd_debug, log),
752 	MYSQLND_METHOD(mysqlnd_debug, log_va),
753 	MYSQLND_METHOD(mysqlnd_debug, func_enter),
754 	MYSQLND_METHOD(mysqlnd_debug, func_leave),
755 	MYSQLND_METHOD(mysqlnd_debug, close),
756 	MYSQLND_METHOD(mysqlnd_debug, free),
757 MYSQLND_CLASS_METHODS_END;
758 
759 
760 /* {{{ mysqlnd_debug_init */
761 PHPAPI MYSQLND_DEBUG *
mysqlnd_debug_init(const char * skip_functions[]TSRMLS_DC)762 mysqlnd_debug_init(const char * skip_functions[] TSRMLS_DC)
763 {
764 	MYSQLND_DEBUG *ret = ecalloc(1, sizeof(MYSQLND_DEBUG));
765 #ifdef ZTS
766 	ret->TSRMLS_C = TSRMLS_C;
767 #endif
768 	ret->nest_level_limit = 0;
769 	ret->pid = getpid();
770 	zend_stack_init(&ret->call_stack);
771 	zend_stack_init(&ret->call_time_stack);
772 	zend_hash_init(&ret->not_filtered_functions, 0, NULL, NULL, 0);
773 	zend_hash_init(&ret->function_profiles, 0, NULL, NULL, 0);
774 
775 	ret->m = & mysqlnd_mysqlnd_debug_methods;
776 	ret->skip_functions = skip_functions;
777 
778 	return ret;
779 }
780 /* }}} */
781 
782 
783 /* {{{ _mysqlnd_debug */
_mysqlnd_debug(const char * mode TSRMLS_DC)784 PHPAPI void _mysqlnd_debug(const char * mode TSRMLS_DC)
785 {
786 #ifdef PHP_DEBUG
787 	MYSQLND_DEBUG *dbg = MYSQLND_G(dbg);
788 	if (!dbg) {
789 		MYSQLND_G(dbg) = dbg = mysqlnd_debug_init(mysqlnd_debug_std_no_trace_funcs TSRMLS_CC);
790 		if (!dbg) {
791 			return;
792 		}
793 	}
794 
795 	dbg->m->close(dbg);
796 	dbg->m->set_mode(dbg, mode);
797 	while (zend_stack_count(&dbg->call_stack)) {
798 		zend_stack_del_top(&dbg->call_stack);
799 	}
800 	while (zend_stack_count(&dbg->call_time_stack)) {
801 		zend_stack_del_top(&dbg->call_time_stack);
802 	}
803 #endif
804 }
805 /* }}} */
806 
807 
808 #if ZEND_DEBUG
809 #else
810 #define __zend_filename "/unknown/unknown"
811 #define __zend_lineno   0
812 #endif
813 
814 #define REAL_SIZE(s) (collect_memory_statistics? (s) + sizeof(size_t) : (s))
815 #define REAL_PTR(p) (collect_memory_statistics && (p)? (((char *)(p)) - sizeof(size_t)) : (p))
816 #define FAKE_PTR(p) (collect_memory_statistics && (p)? (((char *)(p)) + sizeof(size_t)) : (p))
817 
818 /* {{{ _mysqlnd_emalloc */
_mysqlnd_emalloc(size_t size MYSQLND_MEM_D)819 void * _mysqlnd_emalloc(size_t size MYSQLND_MEM_D)
820 {
821 	void *ret;
822 	zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics);
823 	long * threshold = &MYSQLND_G(debug_emalloc_fail_threshold);
824 	DBG_ENTER(mysqlnd_emalloc_name);
825 
826 	DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno);
827 
828 #ifdef PHP_DEBUG
829 	/* -1 is also "true" */
830 	if (*threshold) {
831 #endif
832 		ret = emalloc(REAL_SIZE(size));
833 #ifdef PHP_DEBUG
834 		--*threshold;
835 	} else if (*threshold == 0) {
836 		ret = NULL;
837 	}
838 #endif
839 
840 	DBG_INF_FMT("size=%lu ptr=%p", size, ret);
841 
842 	if (ret && collect_memory_statistics) {
843 		*(size_t *) ret = size;
844 		MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_EMALLOC_COUNT, 1, STAT_MEM_EMALLOC_AMOUNT, size);
845 	}
846 	DBG_RETURN(FAKE_PTR(ret));
847 }
848 /* }}} */
849 
850 
851 /* {{{ _mysqlnd_pemalloc */
_mysqlnd_pemalloc(size_t size,zend_bool persistent MYSQLND_MEM_D)852 void * _mysqlnd_pemalloc(size_t size, zend_bool persistent MYSQLND_MEM_D)
853 {
854 	void *ret;
855 	zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics);
856 	long * threshold = persistent? &MYSQLND_G(debug_malloc_fail_threshold):&MYSQLND_G(debug_emalloc_fail_threshold);
857 	DBG_ENTER(mysqlnd_pemalloc_name);
858 	DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno);
859 
860 #ifdef PHP_DEBUG
861 	/* -1 is also "true" */
862 	if (*threshold) {
863 #endif
864 		ret = pemalloc(REAL_SIZE(size), persistent);
865 #ifdef PHP_DEBUG
866 		--*threshold;
867 	} else if (*threshold == 0) {
868 		ret = NULL;
869 	}
870 #endif
871 
872 	DBG_INF_FMT("size=%lu ptr=%p persistent=%u", size, ret, persistent);
873 
874 	if (ret && collect_memory_statistics) {
875 		enum mysqlnd_collected_stats s1 = persistent? STAT_MEM_MALLOC_COUNT:STAT_MEM_EMALLOC_COUNT;
876 		enum mysqlnd_collected_stats s2 = persistent? STAT_MEM_MALLOC_AMOUNT:STAT_MEM_EMALLOC_AMOUNT;
877 		*(size_t *) ret = size;
878 		MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(s1, 1, s2, size);
879 	}
880 
881 	DBG_RETURN(FAKE_PTR(ret));
882 }
883 /* }}} */
884 
885 
886 /* {{{ _mysqlnd_ecalloc */
_mysqlnd_ecalloc(unsigned int nmemb,size_t size MYSQLND_MEM_D)887 void * _mysqlnd_ecalloc(unsigned int nmemb, size_t size MYSQLND_MEM_D)
888 {
889 	void *ret;
890 	zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics);
891 	long * threshold = &MYSQLND_G(debug_ecalloc_fail_threshold);
892 	DBG_ENTER(mysqlnd_ecalloc_name);
893 	DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno);
894 	DBG_INF_FMT("before: %lu", zend_memory_usage(FALSE TSRMLS_CC));
895 
896 #ifdef PHP_DEBUG
897 	/* -1 is also "true" */
898 	if (*threshold) {
899 #endif
900 		ret = ecalloc(nmemb, REAL_SIZE(size));
901 #ifdef PHP_DEBUG
902 		--*threshold;
903 	} else if (*threshold == 0) {
904 		ret = NULL;
905 	}
906 #endif
907 
908 	DBG_INF_FMT("after : %lu", zend_memory_usage(FALSE TSRMLS_CC));
909 	DBG_INF_FMT("size=%lu ptr=%p", size, ret);
910 	if (ret && collect_memory_statistics) {
911 		*(size_t *) ret = size;
912 		MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_ECALLOC_COUNT, 1, STAT_MEM_ECALLOC_AMOUNT, size);
913 	}
914 	DBG_RETURN(FAKE_PTR(ret));
915 }
916 /* }}} */
917 
918 
919 /* {{{ _mysqlnd_pecalloc */
_mysqlnd_pecalloc(unsigned int nmemb,size_t size,zend_bool persistent MYSQLND_MEM_D)920 void * _mysqlnd_pecalloc(unsigned int nmemb, size_t size, zend_bool persistent MYSQLND_MEM_D)
921 {
922 	void *ret;
923 	zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics);
924 	long * threshold = persistent? &MYSQLND_G(debug_calloc_fail_threshold):&MYSQLND_G(debug_ecalloc_fail_threshold);
925 	DBG_ENTER(mysqlnd_pecalloc_name);
926 	DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno);
927 
928 #ifdef PHP_DEBUG
929 	/* -1 is also "true" */
930 	if (*threshold) {
931 #endif
932 		ret = pecalloc(nmemb, REAL_SIZE(size), persistent);
933 #ifdef PHP_DEBUG
934 		--*threshold;
935 	} else if (*threshold == 0) {
936 		ret = NULL;
937 	}
938 #endif
939 
940 	DBG_INF_FMT("size=%lu ptr=%p", size, ret);
941 
942 	if (ret && collect_memory_statistics) {
943 		enum mysqlnd_collected_stats s1 = persistent? STAT_MEM_CALLOC_COUNT:STAT_MEM_ECALLOC_COUNT;
944 		enum mysqlnd_collected_stats s2 = persistent? STAT_MEM_CALLOC_AMOUNT:STAT_MEM_ECALLOC_AMOUNT;
945 		*(size_t *) ret = size;
946 		MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(s1, 1, s2, size);
947 	}
948 
949 	DBG_RETURN(FAKE_PTR(ret));
950 }
951 /* }}} */
952 
953 
954 /* {{{ _mysqlnd_erealloc */
_mysqlnd_erealloc(void * ptr,size_t new_size MYSQLND_MEM_D)955 void * _mysqlnd_erealloc(void *ptr, size_t new_size MYSQLND_MEM_D)
956 {
957 	void *ret;
958 	zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics);
959 	size_t old_size = collect_memory_statistics && ptr? *(size_t *) (((char*)ptr) - sizeof(size_t)) : 0;
960 	long * threshold = &MYSQLND_G(debug_erealloc_fail_threshold);
961 	DBG_ENTER(mysqlnd_erealloc_name);
962 	DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno);
963 	DBG_INF_FMT("ptr=%p old_size=%lu, new_size=%lu", ptr, old_size, new_size);
964 
965 #ifdef PHP_DEBUG
966 	/* -1 is also "true" */
967 	if (*threshold) {
968 #endif
969 		ret = erealloc(REAL_PTR(ptr), REAL_SIZE(new_size));
970 #ifdef PHP_DEBUG
971 		--*threshold;
972 	} else if (*threshold == 0) {
973 		ret = NULL;
974 	}
975 #endif
976 
977 	DBG_INF_FMT("new_ptr=%p", (char*)ret);
978 	if (ret && collect_memory_statistics) {
979 		*(size_t *) ret = new_size;
980 		MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_EREALLOC_COUNT, 1, STAT_MEM_EREALLOC_AMOUNT, new_size);
981 	}
982 	DBG_RETURN(FAKE_PTR(ret));
983 }
984 /* }}} */
985 
986 
987 /* {{{ _mysqlnd_perealloc */
_mysqlnd_perealloc(void * ptr,size_t new_size,zend_bool persistent MYSQLND_MEM_D)988 void * _mysqlnd_perealloc(void *ptr, size_t new_size, zend_bool persistent MYSQLND_MEM_D)
989 {
990 	void *ret;
991 	zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics);
992 	size_t old_size = collect_memory_statistics && ptr? *(size_t *) (((char*)ptr) - sizeof(size_t)) : 0;
993 	long * threshold = persistent? &MYSQLND_G(debug_realloc_fail_threshold):&MYSQLND_G(debug_erealloc_fail_threshold);
994 	DBG_ENTER(mysqlnd_perealloc_name);
995 	DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno);
996 	DBG_INF_FMT("ptr=%p old_size=%lu new_size=%lu persistent=%u", ptr, old_size, new_size, persistent);
997 
998 #ifdef PHP_DEBUG
999 	/* -1 is also "true" */
1000 	if (*threshold) {
1001 #endif
1002 		ret = perealloc(REAL_PTR(ptr), REAL_SIZE(new_size), persistent);
1003 #ifdef PHP_DEBUG
1004 		--*threshold;
1005 	} else if (*threshold == 0) {
1006 		ret = NULL;
1007 	}
1008 #endif
1009 
1010 	DBG_INF_FMT("new_ptr=%p", (char*)ret);
1011 
1012 	if (ret && collect_memory_statistics) {
1013 		enum mysqlnd_collected_stats s1 = persistent? STAT_MEM_REALLOC_COUNT:STAT_MEM_EREALLOC_COUNT;
1014 		enum mysqlnd_collected_stats s2 = persistent? STAT_MEM_REALLOC_AMOUNT:STAT_MEM_EREALLOC_AMOUNT;
1015 		*(size_t *) ret = new_size;
1016 		MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(s1, 1, s2, new_size);
1017 	}
1018 	DBG_RETURN(FAKE_PTR(ret));
1019 }
1020 /* }}} */
1021 
1022 
1023 /* {{{ _mysqlnd_efree */
_mysqlnd_efree(void * ptr MYSQLND_MEM_D)1024 void _mysqlnd_efree(void *ptr MYSQLND_MEM_D)
1025 {
1026 	size_t free_amount = 0;
1027 	zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics);
1028 	DBG_ENTER(mysqlnd_efree_name);
1029 	DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno);
1030 	DBG_INF_FMT("ptr=%p", ptr);
1031 
1032 	if (ptr) {
1033 		if (collect_memory_statistics) {
1034 			free_amount = *(size_t *)(((char*)ptr) - sizeof(size_t));
1035 			DBG_INF_FMT("ptr=%p size=%u", ((char*)ptr) - sizeof(size_t), (unsigned int) free_amount);
1036 		}
1037 		efree(REAL_PTR(ptr));
1038 	}
1039 
1040 	if (collect_memory_statistics) {
1041 		MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_EFREE_COUNT, 1, STAT_MEM_EFREE_AMOUNT, free_amount);
1042 	}
1043 	DBG_VOID_RETURN;
1044 }
1045 /* }}} */
1046 
1047 
1048 /* {{{ _mysqlnd_pefree */
_mysqlnd_pefree(void * ptr,zend_bool persistent MYSQLND_MEM_D)1049 void _mysqlnd_pefree(void *ptr, zend_bool persistent MYSQLND_MEM_D)
1050 {
1051 	size_t free_amount = 0;
1052 	zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics);
1053 	DBG_ENTER(mysqlnd_pefree_name);
1054 	DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno);
1055 	DBG_INF_FMT("ptr=%p persistent=%u", ptr, persistent);
1056 
1057 	if (ptr) {
1058 		if (collect_memory_statistics) {
1059 			free_amount = *(size_t *)(((char*)ptr) - sizeof(size_t));
1060 			DBG_INF_FMT("ptr=%p size=%u", ((char*)ptr) - sizeof(size_t), (unsigned int) free_amount);
1061 		}
1062 		pefree(REAL_PTR(ptr), persistent);
1063 	}
1064 
1065 	if (collect_memory_statistics) {
1066 		MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(persistent? STAT_MEM_FREE_COUNT:STAT_MEM_EFREE_COUNT, 1,
1067 											  persistent? STAT_MEM_FREE_AMOUNT:STAT_MEM_EFREE_AMOUNT, free_amount);
1068 	}
1069 	DBG_VOID_RETURN;
1070 }
1071 
1072 
1073 /* {{{ _mysqlnd_malloc */
_mysqlnd_malloc(size_t size MYSQLND_MEM_D)1074 void * _mysqlnd_malloc(size_t size MYSQLND_MEM_D)
1075 {
1076 	void *ret;
1077 	zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics);
1078 	long * threshold = &MYSQLND_G(debug_malloc_fail_threshold);
1079 	DBG_ENTER(mysqlnd_malloc_name);
1080 	DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno);
1081 
1082 #ifdef PHP_DEBUG
1083 	/* -1 is also "true" */
1084 	if (*threshold) {
1085 #endif
1086 		ret = malloc(REAL_SIZE(size));
1087 #ifdef PHP_DEBUG
1088 		--*threshold;
1089 	} else if (*threshold == 0) {
1090 		ret = NULL;
1091 	}
1092 #endif
1093 
1094 	DBG_INF_FMT("size=%lu ptr=%p", size, ret);
1095 	if (ret && collect_memory_statistics) {
1096 		*(size_t *) ret = size;
1097 		MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_MALLOC_COUNT, 1, STAT_MEM_MALLOC_AMOUNT, size);
1098 	}
1099 	DBG_RETURN(FAKE_PTR(ret));
1100 }
1101 /* }}} */
1102 
1103 
1104 /* {{{ _mysqlnd_calloc */
_mysqlnd_calloc(unsigned int nmemb,size_t size MYSQLND_MEM_D)1105 void * _mysqlnd_calloc(unsigned int nmemb, size_t size MYSQLND_MEM_D)
1106 {
1107 	void *ret;
1108 	zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics);
1109 	long * threshold = &MYSQLND_G(debug_calloc_fail_threshold);
1110 	DBG_ENTER(mysqlnd_calloc_name);
1111 	DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno);
1112 
1113 #ifdef PHP_DEBUG
1114 	/* -1 is also "true" */
1115 	if (*threshold) {
1116 #endif
1117 		ret = calloc(nmemb, REAL_SIZE(size));
1118 #ifdef PHP_DEBUG
1119 		--*threshold;
1120 	} else if (*threshold == 0) {
1121 		ret = NULL;
1122 	}
1123 #endif
1124 
1125 	DBG_INF_FMT("size=%lu ptr=%p", size, ret);
1126 	if (ret && collect_memory_statistics) {
1127 		*(size_t *) ret = size;
1128 		MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_CALLOC_COUNT, 1, STAT_MEM_CALLOC_AMOUNT, size);
1129 	}
1130 	DBG_RETURN(FAKE_PTR(ret));
1131 }
1132 /* }}} */
1133 
1134 
1135 /* {{{ _mysqlnd_realloc */
_mysqlnd_realloc(void * ptr,size_t new_size MYSQLND_MEM_D)1136 void * _mysqlnd_realloc(void *ptr, size_t new_size MYSQLND_MEM_D)
1137 {
1138 	void *ret;
1139 	zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics);
1140 	long * threshold = &MYSQLND_G(debug_realloc_fail_threshold);
1141 	DBG_ENTER(mysqlnd_realloc_name);
1142 	DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno);
1143 	DBG_INF_FMT("ptr=%p new_size=%lu ", new_size, ptr);
1144 	DBG_INF_FMT("before: %lu", zend_memory_usage(TRUE TSRMLS_CC));
1145 
1146 #ifdef PHP_DEBUG
1147 	/* -1 is also "true" */
1148 	if (*threshold) {
1149 #endif
1150 		ret = realloc(REAL_PTR(ptr), REAL_SIZE(new_size));
1151 #ifdef PHP_DEBUG
1152 		--*threshold;
1153 	} else if (*threshold == 0) {
1154 		ret = NULL;
1155 	}
1156 #endif
1157 
1158 	DBG_INF_FMT("new_ptr=%p", (char*)ret);
1159 
1160 	if (ret && collect_memory_statistics) {
1161 		*(size_t *) ret = new_size;
1162 		MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_REALLOC_COUNT, 1, STAT_MEM_REALLOC_AMOUNT, new_size);
1163 	}
1164 	DBG_RETURN(FAKE_PTR(ret));
1165 }
1166 /* }}} */
1167 
1168 
1169 /* {{{ _mysqlnd_free */
_mysqlnd_free(void * ptr MYSQLND_MEM_D)1170 void _mysqlnd_free(void *ptr MYSQLND_MEM_D)
1171 {
1172 	size_t free_amount = 0;
1173 	zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics);
1174 	DBG_ENTER(mysqlnd_free_name);
1175 	DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno);
1176 	DBG_INF_FMT("ptr=%p", ptr);
1177 
1178 	if (ptr) {
1179 		if (collect_memory_statistics) {
1180 			free_amount = *(size_t *)(((char*)ptr) - sizeof(size_t));
1181 			DBG_INF_FMT("ptr=%p size=%u", ((char*)ptr) - sizeof(size_t), (unsigned int) free_amount);
1182 		}
1183 		free(REAL_PTR(ptr));
1184 	}
1185 
1186 	if (collect_memory_statistics) {
1187 		MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_FREE_COUNT, 1, STAT_MEM_FREE_AMOUNT, free_amount);
1188 	}
1189 	DBG_VOID_RETURN;
1190 }
1191 /* }}} */
1192 
1193 #define SMART_STR_START_SIZE 2048
1194 #define SMART_STR_PREALLOC 512
1195 #include "ext/standard/php_smart_str.h"
1196 
1197 
1198 /* {{{ _mysqlnd_pestrndup */
_mysqlnd_pestrndup(const char * const ptr,size_t length,zend_bool persistent MYSQLND_MEM_D)1199 char * _mysqlnd_pestrndup(const char * const ptr, size_t length, zend_bool persistent MYSQLND_MEM_D)
1200 {
1201 	char * ret;
1202 	zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics);
1203 	DBG_ENTER(mysqlnd_pestrndup_name);
1204 	DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno);
1205 	DBG_INF_FMT("ptr=%p", ptr);
1206 
1207 	ret = pemalloc(REAL_SIZE(length) + 1, persistent);
1208 	{
1209 		size_t l = length;
1210 		char * p = (char *) ptr;
1211 		char * dest = (char *) FAKE_PTR(ret);
1212 		while (*p && l--) {
1213 			*dest++ = *p++;
1214 		}
1215 		*dest = '\0';
1216 	}
1217 
1218 	if (collect_memory_statistics) {
1219 		*(size_t *) ret = length;
1220 		MYSQLND_INC_GLOBAL_STATISTIC(persistent? STAT_MEM_STRNDUP_COUNT : STAT_MEM_ESTRNDUP_COUNT);
1221 	}
1222 
1223 	DBG_RETURN(FAKE_PTR(ret));
1224 }
1225 /* }}} */
1226 
1227 
1228 /* {{{ _mysqlnd_pestrdup */
_mysqlnd_pestrdup(const char * const ptr,zend_bool persistent MYSQLND_MEM_D)1229 char * _mysqlnd_pestrdup(const char * const ptr, zend_bool persistent MYSQLND_MEM_D)
1230 {
1231 	char * ret;
1232 	smart_str tmp_str = {0, 0, 0};
1233 	const char * p = ptr;
1234 	zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics);
1235 	DBG_ENTER(mysqlnd_pestrdup_name);
1236 	DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno);
1237 	DBG_INF_FMT("ptr=%p", ptr);
1238 	do {
1239 		smart_str_appendc(&tmp_str, *p);
1240 	} while (*p++);
1241 
1242 	ret = pemalloc(tmp_str.len + sizeof(size_t), persistent);
1243 	memcpy(FAKE_PTR(ret), tmp_str.c, tmp_str.len);
1244 
1245 	if (ret && collect_memory_statistics) {
1246 		*(size_t *) ret = tmp_str.len;
1247 		MYSQLND_INC_GLOBAL_STATISTIC(persistent? STAT_MEM_STRDUP_COUNT : STAT_MEM_ESTRDUP_COUNT);
1248 	}
1249 	smart_str_free(&tmp_str);
1250 
1251 	DBG_RETURN(FAKE_PTR(ret));
1252 }
1253 /* }}} */
1254 
1255 #define MYSQLND_DEBUG_MEMORY 1
1256 
1257 #if MYSQLND_DEBUG_MEMORY == 0
1258 
1259 /* {{{ mysqlnd_zend_mm_emalloc */
mysqlnd_zend_mm_emalloc(size_t size MYSQLND_MEM_D)1260 static void * mysqlnd_zend_mm_emalloc(size_t size MYSQLND_MEM_D)
1261 {
1262 	return emalloc(size);
1263 }
1264 /* }}} */
1265 
1266 
1267 /* {{{ mysqlnd_zend_mm_pemalloc */
mysqlnd_zend_mm_pemalloc(size_t size,zend_bool persistent MYSQLND_MEM_D)1268 static void * mysqlnd_zend_mm_pemalloc(size_t size, zend_bool persistent MYSQLND_MEM_D)
1269 {
1270 	return pemalloc(size, persistent);
1271 }
1272 /* }}} */
1273 
1274 
1275 /* {{{ mysqlnd_zend_mm_ecalloc */
mysqlnd_zend_mm_ecalloc(unsigned int nmemb,size_t size MYSQLND_MEM_D)1276 static void * mysqlnd_zend_mm_ecalloc(unsigned int nmemb, size_t size MYSQLND_MEM_D)
1277 {
1278 	return ecalloc(nmemb, size);
1279 }
1280 /* }}} */
1281 
1282 
1283 /* {{{ mysqlnd_zend_mm_pecalloc */
mysqlnd_zend_mm_pecalloc(unsigned int nmemb,size_t size,zend_bool persistent MYSQLND_MEM_D)1284 static void * mysqlnd_zend_mm_pecalloc(unsigned int nmemb, size_t size, zend_bool persistent MYSQLND_MEM_D)
1285 {
1286 	return pecalloc(nmemb, size, persistent);
1287 }
1288 /* }}} */
1289 
1290 
1291 /* {{{ mysqlnd_zend_mm_erealloc */
mysqlnd_zend_mm_erealloc(void * ptr,size_t new_size MYSQLND_MEM_D)1292 static void * mysqlnd_zend_mm_erealloc(void *ptr, size_t new_size MYSQLND_MEM_D)
1293 {
1294 	return erealloc(ptr, new_size);
1295 }
1296 /* }}} */
1297 
1298 
1299 /* {{{ mysqlnd_zend_mm_perealloc */
mysqlnd_zend_mm_perealloc(void * ptr,size_t new_size,zend_bool persistent MYSQLND_MEM_D)1300 static void * mysqlnd_zend_mm_perealloc(void *ptr, size_t new_size, zend_bool persistent MYSQLND_MEM_D)
1301 {
1302 	return perealloc(ptr, new_size, persistent);
1303 }
1304 /* }}} */
1305 
1306 
1307 /* {{{ mysqlnd_zend_mm_efree */
mysqlnd_zend_mm_efree(void * ptr MYSQLND_MEM_D)1308 static void mysqlnd_zend_mm_efree(void * ptr MYSQLND_MEM_D)
1309 {
1310 	efree(ptr);
1311 }
1312 /* }}} */
1313 
1314 
1315 /* {{{ mysqlnd_zend_mm_pefree */
mysqlnd_zend_mm_pefree(void * ptr,zend_bool persistent MYSQLND_MEM_D)1316 static void mysqlnd_zend_mm_pefree(void * ptr, zend_bool persistent MYSQLND_MEM_D)
1317 {
1318 	pefree(ptr, persistent);
1319 }
1320 /* }}} */
1321 
1322 
1323 /* {{{ mysqlnd_zend_mm_malloc */
mysqlnd_zend_mm_malloc(size_t size MYSQLND_MEM_D)1324 static void * mysqlnd_zend_mm_malloc(size_t size MYSQLND_MEM_D)
1325 {
1326 	return malloc(size);
1327 }
1328 /* }}} */
1329 
1330 
1331 /* {{{ mysqlnd_zend_mm_calloc */
mysqlnd_zend_mm_calloc(unsigned int nmemb,size_t size MYSQLND_MEM_D)1332 static void * mysqlnd_zend_mm_calloc(unsigned int nmemb, size_t size MYSQLND_MEM_D)
1333 {
1334 	return calloc(nmemb, size);
1335 }
1336 /* }}} */
1337 
1338 
1339 /* {{{ mysqlnd_zend_mm_realloc */
mysqlnd_zend_mm_realloc(void * ptr,size_t new_size MYSQLND_MEM_D)1340 static void * mysqlnd_zend_mm_realloc(void * ptr, size_t new_size MYSQLND_MEM_D)
1341 {
1342 	return realloc(ptr, new_size);
1343 }
1344 /* }}} */
1345 
1346 
1347 /* {{{ mysqlnd_zend_mm_free */
mysqlnd_zend_mm_free(void * ptr MYSQLND_MEM_D)1348 static void mysqlnd_zend_mm_free(void * ptr MYSQLND_MEM_D)
1349 {
1350 	free(ptr);
1351 }
1352 /* }}} */
1353 
1354 
1355 /* {{{ mysqlnd_zend_mm_pestrndup */
mysqlnd_zend_mm_pestrndup(const char * const ptr,size_t length,zend_bool persistent MYSQLND_MEM_D)1356 static char * mysqlnd_zend_mm_pestrndup(const char * const ptr, size_t length, zend_bool persistent MYSQLND_MEM_D)
1357 {
1358 	return pestrndup(ptr, length, persistent);
1359 }
1360 /* }}} */
1361 
1362 
1363 /* {{{ mysqlnd_zend_mm_pestrdup */
mysqlnd_zend_mm_pestrdup(const char * const ptr,zend_bool persistent MYSQLND_MEM_D)1364 static char * mysqlnd_zend_mm_pestrdup(const char * const ptr, zend_bool persistent MYSQLND_MEM_D)
1365 {
1366 	return pestrdup(ptr, persistent);
1367 }
1368 /* }}} */
1369 
1370 #endif
1371 
1372 
1373 PHPAPI struct st_mysqlnd_allocator_methods mysqlnd_allocator =
1374 {
1375 #if MYSQLND_DEBUG_MEMORY
1376 	_mysqlnd_emalloc,
1377 	_mysqlnd_pemalloc,
1378 	_mysqlnd_ecalloc,
1379 	_mysqlnd_pecalloc,
1380 	_mysqlnd_erealloc,
1381 	_mysqlnd_perealloc,
1382 	_mysqlnd_efree,
1383 	_mysqlnd_pefree,
1384 	_mysqlnd_malloc,
1385 	_mysqlnd_calloc,
1386 	_mysqlnd_realloc,
1387 	_mysqlnd_free,
1388 	_mysqlnd_pestrndup,
1389 	_mysqlnd_pestrdup
1390 #else
1391 	mysqlnd_zend_mm_emalloc,
1392 	mysqlnd_zend_mm_pemalloc,
1393 	mysqlnd_zend_mm_ecalloc,
1394 	mysqlnd_zend_mm_pecalloc,
1395 	mysqlnd_zend_mm_erealloc,
1396 	mysqlnd_zend_mm_perealloc,
1397 	mysqlnd_zend_mm_efree,
1398 	mysqlnd_zend_mm_pefree,
1399 	mysqlnd_zend_mm_malloc,
1400 	mysqlnd_zend_mm_calloc,
1401 	mysqlnd_zend_mm_realloc,
1402 	mysqlnd_zend_mm_free,
1403 	mysqlnd_zend_mm_pestrndup,
1404 	mysqlnd_zend_mm_pestrdup
1405 #endif
1406 };
1407 
1408 
1409 
1410 /* Follows code borrowed from zend_builtin_functions.c because the functions there are static */
1411 
1412 #if MYSQLND_UNICODE
1413 /* {{{ gettraceasstring() macros */
1414 #define TRACE_APPEND_CHR(chr)                                            \
1415 	*str = (char*)erealloc(*str, *len + 1 + 1);                          \
1416 	(*str)[(*len)++] = chr
1417 
1418 #define TRACE_APPEND_STRL(val, vallen)                                   \
1419 	{                                                                    \
1420 		int l = vallen;                                                  \
1421 		*str = (char*)erealloc(*str, *len + l + 1);                      \
1422 		memcpy((*str) + *len, val, l);                                   \
1423 		*len += l;                                                       \
1424 	}
1425 
1426 #define TRACE_APPEND_USTRL(val, vallen) \
1427 	{ \
1428 		zval tmp, copy; \
1429 		int use_copy; \
1430 		ZVAL_UNICODEL(&tmp, val, vallen, 1); \
1431 		zend_make_printable_zval(&tmp, &copy, &use_copy); \
1432 		TRACE_APPEND_STRL(Z_STRVAL(copy), Z_STRLEN(copy)); \
1433 		zval_dtor(&copy); \
1434 		zval_dtor(&tmp); \
1435 	}
1436 
1437 #define TRACE_APPEND_ZVAL(zv) \
1438 	if (Z_TYPE_P((zv)) == IS_UNICODE) { \
1439 		zval copy; \
1440 		int use_copy; \
1441 		zend_make_printable_zval((zv), &copy, &use_copy); \
1442 		TRACE_APPEND_STRL(Z_STRVAL(copy), Z_STRLEN(copy)); \
1443 		zval_dtor(&copy); \
1444 	} else { \
1445 		TRACE_APPEND_STRL(Z_STRVAL_P((zv)), Z_STRLEN_P((zv))); \
1446 	}
1447 
1448 #define TRACE_APPEND_STR(val)                                            \
1449 	TRACE_APPEND_STRL(val, sizeof(val)-1)
1450 
1451 #define TRACE_APPEND_KEY(key)                                            \
1452 	if (zend_ascii_hash_find(ht, key, sizeof(key), (void**)&tmp) == SUCCESS) { \
1453 		if (Z_TYPE_PP(tmp) == IS_UNICODE) { \
1454 			zval copy; \
1455 			int use_copy; \
1456 			zend_make_printable_zval(*tmp, &copy, &use_copy); \
1457 			TRACE_APPEND_STRL(Z_STRVAL(copy), Z_STRLEN(copy)); \
1458 			zval_dtor(&copy); \
1459 		} else { \
1460 		TRACE_APPEND_STRL(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp));           \
1461 		} \
1462 	}
1463 /* }}} */
1464 
1465 /* {{{ mysqlnd_build_trace_args */
mysqlnd_build_trace_args(zval ** arg TSRMLS_DC,int num_args,va_list args,zend_hash_key * hash_key)1466 static int mysqlnd_build_trace_args(zval **arg TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key)
1467 {
1468 	char **str;
1469 	int *len;
1470 
1471 	str = va_arg(args, char**);
1472 	len = va_arg(args, int*);
1473 
1474 	/* the trivial way would be to do:
1475 	 * conver_to_string_ex(arg);
1476 	 * append it and kill the now tmp arg.
1477 	 * but that could cause some E_NOTICE and also damn long lines.
1478 	 */
1479 
1480 	switch (Z_TYPE_PP(arg)) {
1481 		case IS_NULL:
1482 			TRACE_APPEND_STR("NULL, ");
1483 			break;
1484 		case IS_STRING: {
1485 			int l_added;
1486 			TRACE_APPEND_CHR('\'');
1487 			if (Z_STRLEN_PP(arg) > 15) {
1488 				TRACE_APPEND_STRL(Z_STRVAL_PP(arg), 15);
1489 				TRACE_APPEND_STR("...', ");
1490 				l_added = 15 + 6 + 1; /* +1 because of while (--l_added) */
1491 			} else {
1492 				l_added = Z_STRLEN_PP(arg);
1493 				TRACE_APPEND_STRL(Z_STRVAL_PP(arg), l_added);
1494 				TRACE_APPEND_STR("', ");
1495 				l_added += 3 + 1;
1496 			}
1497 			while (--l_added) {
1498 				if ((unsigned char)(*str)[*len - l_added] < 32) {
1499 					(*str)[*len - l_added] = '?';
1500 				}
1501 			}
1502 			break;
1503 		}
1504 		case IS_UNICODE: {
1505 			int l_added;
1506 
1507 			/*
1508 			 * We do not want to apply current error mode here, since
1509 			 * zend_make_printable_zval() uses output encoding converter.
1510 			 * Temporarily set output encoding converter to escape offending
1511 			 * chars with \uXXXX notation.
1512 			 */
1513 			zend_set_converter_error_mode(ZEND_U_CONVERTER(UG(output_encoding_conv)), ZEND_FROM_UNICODE, ZEND_CONV_ERROR_ESCAPE_JAVA);
1514 			TRACE_APPEND_CHR('\'');
1515 			if (Z_USTRLEN_PP(arg) > 15) {
1516 				TRACE_APPEND_USTRL(Z_USTRVAL_PP(arg), 15);
1517 				TRACE_APPEND_STR("...', ");
1518 				l_added = 15 + 6 + 1; /* +1 because of while (--l_added) */
1519 			} else {
1520 				l_added = Z_USTRLEN_PP(arg);
1521 				TRACE_APPEND_USTRL(Z_USTRVAL_PP(arg), l_added);
1522 				TRACE_APPEND_STR("', ");
1523 				l_added += 3 + 1;
1524 			}
1525 			/*
1526 			 * Reset output encoding converter error mode.
1527 			 */
1528 			zend_set_converter_error_mode(ZEND_U_CONVERTER(UG(output_encoding_conv)), ZEND_FROM_UNICODE, UG(from_error_mode));
1529 			while (--l_added) {
1530 				if ((unsigned char)(*str)[*len - l_added] < 32) {
1531 					(*str)[*len - l_added] = '?';
1532 				}
1533 			}
1534 			break;
1535 		}
1536 		case IS_BOOL:
1537 			if (Z_LVAL_PP(arg)) {
1538 				TRACE_APPEND_STR("true, ");
1539 			} else {
1540 				TRACE_APPEND_STR("false, ");
1541 			}
1542 			break;
1543 		case IS_RESOURCE:
1544 			TRACE_APPEND_STR("Resource id #");
1545 			/* break; */
1546 		case IS_LONG: {
1547 			long lval = Z_LVAL_PP(arg);
1548 			char s_tmp[MAX_LENGTH_OF_LONG + 1];
1549 			int l_tmp = zend_sprintf(s_tmp, "%ld", lval);  /* SAFE */
1550 			TRACE_APPEND_STRL(s_tmp, l_tmp);
1551 			TRACE_APPEND_STR(", ");
1552 			break;
1553 		}
1554 		case IS_DOUBLE: {
1555 			double dval = Z_DVAL_PP(arg);
1556 			char *s_tmp;
1557 			int l_tmp;
1558 
1559 			s_tmp = emalloc(MAX_LENGTH_OF_DOUBLE + EG(precision) + 1);
1560 			l_tmp = zend_sprintf(s_tmp, "%.*G", (int) EG(precision), dval);  /* SAFE */
1561 			TRACE_APPEND_STRL(s_tmp, l_tmp);
1562 			/* %G already handles removing trailing zeros from the fractional part, yay */
1563 			efree(s_tmp);
1564 			TRACE_APPEND_STR(", ");
1565 			break;
1566 		}
1567 		case IS_ARRAY:
1568 			TRACE_APPEND_STR("Array, ");
1569 			break;
1570 		case IS_OBJECT: {
1571 			zval tmp;
1572 			zstr class_name;
1573 			zend_uint class_name_len;
1574 			int dup;
1575 
1576 			TRACE_APPEND_STR("Object(");
1577 
1578 			dup = zend_get_object_classname(*arg, &class_name, &class_name_len TSRMLS_CC);
1579 
1580 			ZVAL_UNICODEL(&tmp, class_name.u, class_name_len, 1);
1581 			convert_to_string_with_converter(&tmp, ZEND_U_CONVERTER(UG(output_encoding_conv)));
1582 			TRACE_APPEND_STRL(Z_STRVAL(tmp), Z_STRLEN(tmp));
1583 			zval_dtor(&tmp);
1584 
1585 			if(!dup) {
1586 				efree(class_name.v);
1587 			}
1588 
1589 			TRACE_APPEND_STR("), ");
1590 			break;
1591 		}
1592 		default:
1593 			break;
1594 	}
1595 	return ZEND_HASH_APPLY_KEEP;
1596 }
1597 /* }}} */
1598 
1599 
mysqlnd_build_trace_string(zval ** frame TSRMLS_DC,int num_args,va_list args,zend_hash_key * hash_key)1600 static int mysqlnd_build_trace_string(zval **frame TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) /* {{{ */
1601 {
1602 	char *s_tmp, **str;
1603 	int *len, *num;
1604 	long line;
1605 	HashTable *ht = Z_ARRVAL_PP(frame);
1606 	zval **file, **tmp;
1607 	uint * level;
1608 
1609 	level = va_arg(args, uint *);
1610 	str = va_arg(args, char**);
1611 	len = va_arg(args, int*);
1612 	num = va_arg(args, int*);
1613 
1614 	if (!*level) {
1615 		return ZEND_HASH_APPLY_KEEP;
1616 	}
1617 	--*level;
1618 
1619 	s_tmp = emalloc(1 + MAX_LENGTH_OF_LONG + 1 + 1);
1620 	sprintf(s_tmp, "#%d ", (*num)++);
1621 	TRACE_APPEND_STRL(s_tmp, strlen(s_tmp));
1622 	efree(s_tmp);
1623 	if (zend_ascii_hash_find(ht, "file", sizeof("file"), (void**)&file) == SUCCESS) {
1624 		if (zend_ascii_hash_find(ht, "line", sizeof("line"), (void**)&tmp) == SUCCESS) {
1625 			line = Z_LVAL_PP(tmp);
1626 		} else {
1627 			line = 0;
1628 		}
1629 		TRACE_APPEND_ZVAL(*file);
1630 		s_tmp = emalloc(MAX_LENGTH_OF_LONG + 2 + 1);
1631 		sprintf(s_tmp, "(%ld): ", line);
1632 		TRACE_APPEND_STRL(s_tmp, strlen(s_tmp));
1633 		efree(s_tmp);
1634 	} else {
1635 		TRACE_APPEND_STR("[internal function]: ");
1636 	}
1637 	TRACE_APPEND_KEY("class");
1638 	TRACE_APPEND_KEY("type");
1639 	TRACE_APPEND_KEY("function");
1640 	TRACE_APPEND_CHR('(');
1641 	if (zend_ascii_hash_find(ht, "args", sizeof("args"), (void**)&tmp) == SUCCESS) {
1642 		int last_len = *len;
1643 		zend_hash_apply_with_arguments(Z_ARRVAL_PP(tmp) TSRMLS_CC, (apply_func_args_t)mysqlnd_build_trace_args, 2, str, len);
1644 		if (last_len != *len) {
1645 			*len -= 2; /* remove last ', ' */
1646 		}
1647 	}
1648 	TRACE_APPEND_STR(")\n");
1649 	return ZEND_HASH_APPLY_KEEP;
1650 }
1651 /* }}} */
1652 
1653 
1654 #else /* PHP 5*/
1655 
1656 
1657 /* {{{ gettraceasstring() macros */
1658 #define TRACE_APPEND_CHR(chr)                                            \
1659 	*str = (char*)erealloc(*str, *len + 1 + 1);                          \
1660 	(*str)[(*len)++] = chr
1661 
1662 #define TRACE_APPEND_STRL(val, vallen)                                   \
1663 	{                                                                    \
1664 		int l = vallen;                                                  \
1665 		*str = (char*)erealloc(*str, *len + l + 1);                      \
1666 		memcpy((*str) + *len, val, l);                                   \
1667 		*len += l;                                                       \
1668 	}
1669 
1670 #define TRACE_APPEND_STR(val)                                            \
1671 	TRACE_APPEND_STRL(val, sizeof(val)-1)
1672 
1673 #define TRACE_APPEND_KEY(key)                                            \
1674 	if (zend_hash_find(ht, key, sizeof(key), (void**)&tmp) == SUCCESS) { \
1675 	    TRACE_APPEND_STRL(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp));           \
1676 	}
1677 
1678 /* }}} */
1679 
1680 
mysqlnd_build_trace_args(zval ** arg TSRMLS_DC,int num_args,va_list args,zend_hash_key * hash_key)1681 static int mysqlnd_build_trace_args(zval **arg TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) /* {{{ */
1682 {
1683 	char **str;
1684 	int *len;
1685 
1686 	str = va_arg(args, char**);
1687 	len = va_arg(args, int*);
1688 
1689 	/* the trivial way would be to do:
1690 	 * conver_to_string_ex(arg);
1691 	 * append it and kill the now tmp arg.
1692 	 * but that could cause some E_NOTICE and also damn long lines.
1693 	 */
1694 
1695 	switch (Z_TYPE_PP(arg)) {
1696 		case IS_NULL:
1697 			TRACE_APPEND_STR("NULL, ");
1698 			break;
1699 		case IS_STRING: {
1700 			int l_added;
1701 			TRACE_APPEND_CHR('\'');
1702 			if (Z_STRLEN_PP(arg) > 15) {
1703 				TRACE_APPEND_STRL(Z_STRVAL_PP(arg), 15);
1704 				TRACE_APPEND_STR("...', ");
1705 				l_added = 15 + 6 + 1; /* +1 because of while (--l_added) */
1706 			} else {
1707 				l_added = Z_STRLEN_PP(arg);
1708 				TRACE_APPEND_STRL(Z_STRVAL_PP(arg), l_added);
1709 				TRACE_APPEND_STR("', ");
1710 				l_added += 3 + 1;
1711 			}
1712 			while (--l_added) {
1713 				if ((*str)[*len - l_added] < 32) {
1714 					(*str)[*len - l_added] = '?';
1715 				}
1716 			}
1717 			break;
1718 		}
1719 		case IS_BOOL:
1720 			if (Z_LVAL_PP(arg)) {
1721 				TRACE_APPEND_STR("true, ");
1722 			} else {
1723 				TRACE_APPEND_STR("false, ");
1724 			}
1725 			break;
1726 		case IS_RESOURCE:
1727 			TRACE_APPEND_STR("Resource id #");
1728 			/* break; */
1729 		case IS_LONG: {
1730 			long lval = Z_LVAL_PP(arg);
1731 			char s_tmp[MAX_LENGTH_OF_LONG + 1];
1732 			int l_tmp = zend_sprintf(s_tmp, "%ld", lval);  /* SAFE */
1733 			TRACE_APPEND_STRL(s_tmp, l_tmp);
1734 			TRACE_APPEND_STR(", ");
1735 			break;
1736 		}
1737 		case IS_DOUBLE: {
1738 			double dval = Z_DVAL_PP(arg);
1739 			char *s_tmp;
1740 			int l_tmp;
1741 
1742 			s_tmp = emalloc(MAX_LENGTH_OF_DOUBLE + EG(precision) + 1);
1743 			l_tmp = zend_sprintf(s_tmp, "%.*G", (int) EG(precision), dval);  /* SAFE */
1744 			TRACE_APPEND_STRL(s_tmp, l_tmp);
1745 			/* %G already handles removing trailing zeros from the fractional part, yay */
1746 			efree(s_tmp);
1747 			TRACE_APPEND_STR(", ");
1748 			break;
1749 		}
1750 		case IS_ARRAY:
1751 			TRACE_APPEND_STR("Array, ");
1752 			break;
1753 		case IS_OBJECT: {
1754 			char *class_name;
1755 			zend_uint class_name_len;
1756 			int dupl;
1757 
1758 			TRACE_APPEND_STR("Object(");
1759 
1760 			dupl = zend_get_object_classname(*arg, &class_name, &class_name_len TSRMLS_CC);
1761 
1762 			TRACE_APPEND_STRL(class_name, class_name_len);
1763 			if (!dupl) {
1764 				efree(class_name);
1765 			}
1766 
1767 			TRACE_APPEND_STR("), ");
1768 			break;
1769 		}
1770 		default:
1771 			break;
1772 	}
1773 	return ZEND_HASH_APPLY_KEEP;
1774 }
1775 /* }}} */
1776 
mysqlnd_build_trace_string(zval ** frame TSRMLS_DC,int num_args,va_list args,zend_hash_key * hash_key)1777 static int mysqlnd_build_trace_string(zval **frame TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) /* {{{ */
1778 {
1779 	char *s_tmp, **str;
1780 	int *len, *num;
1781 	long line;
1782 	HashTable *ht = Z_ARRVAL_PP(frame);
1783 	zval **file, **tmp;
1784 	uint * level;
1785 
1786 	level = va_arg(args, uint *);
1787 	str = va_arg(args, char**);
1788 	len = va_arg(args, int*);
1789 	num = va_arg(args, int*);
1790 
1791 	if (!*level) {
1792 		return ZEND_HASH_APPLY_KEEP;
1793 	}
1794 	--*level;
1795 
1796 	s_tmp = emalloc(1 + MAX_LENGTH_OF_LONG + 1 + 1);
1797 	sprintf(s_tmp, "#%d ", (*num)++);
1798 	TRACE_APPEND_STRL(s_tmp, strlen(s_tmp));
1799 	efree(s_tmp);
1800 	if (zend_hash_find(ht, "file", sizeof("file"), (void**)&file) == SUCCESS) {
1801 		if (zend_hash_find(ht, "line", sizeof("line"), (void**)&tmp) == SUCCESS) {
1802 			line = Z_LVAL_PP(tmp);
1803 		} else {
1804 			line = 0;
1805 		}
1806 		s_tmp = emalloc(Z_STRLEN_PP(file) + MAX_LENGTH_OF_LONG + 4 + 1);
1807 		sprintf(s_tmp, "%s(%ld): ", Z_STRVAL_PP(file), line);
1808 		TRACE_APPEND_STRL(s_tmp, strlen(s_tmp));
1809 		efree(s_tmp);
1810 	} else {
1811 		TRACE_APPEND_STR("[internal function]: ");
1812 	}
1813 	TRACE_APPEND_KEY("class");
1814 	TRACE_APPEND_KEY("type");
1815 	TRACE_APPEND_KEY("function");
1816 	TRACE_APPEND_CHR('(');
1817 	if (zend_hash_find(ht, "args", sizeof("args"), (void**)&tmp) == SUCCESS) {
1818 		int last_len = *len;
1819 		zend_hash_apply_with_arguments(Z_ARRVAL_PP(tmp) TSRMLS_CC, (apply_func_args_t)mysqlnd_build_trace_args, 2, str, len);
1820 		if (last_len != *len) {
1821 			*len -= 2; /* remove last ', ' */
1822 		}
1823 	}
1824 	TRACE_APPEND_STR(")\n");
1825 	return ZEND_HASH_APPLY_KEEP;
1826 }
1827 /* }}} */
1828 #endif
1829 
1830 
mysqlnd_get_backtrace(uint max_levels,size_t * length TSRMLS_DC)1831 PHPAPI char * mysqlnd_get_backtrace(uint max_levels, size_t * length TSRMLS_DC)
1832 {
1833 	zval *trace;
1834 	char *res = estrdup(""), **str = &res, *s_tmp;
1835 	int res_len = 0, *len = &res_len, num = 0;
1836 	if (max_levels == 0) {
1837 		max_levels = 99999;
1838 	}
1839 
1840 	MAKE_STD_ZVAL(trace);
1841 	zend_fetch_debug_backtrace(trace, 0, 0 TSRMLS_CC);
1842 
1843 	zend_hash_apply_with_arguments(Z_ARRVAL_P(trace) TSRMLS_CC, (apply_func_args_t)mysqlnd_build_trace_string, 4, &max_levels, str, len, &num);
1844 	zval_ptr_dtor(&trace);
1845 
1846 	if (max_levels) {
1847 		s_tmp = emalloc(1 + MAX_LENGTH_OF_LONG + 7 + 1);
1848 		sprintf(s_tmp, "#%d {main}", num);
1849 		TRACE_APPEND_STRL(s_tmp, strlen(s_tmp));
1850 		efree(s_tmp);
1851 	}
1852 
1853 	res[res_len] = '\0';
1854 	*length = res_len;
1855 
1856 	return res;
1857 }
1858 
1859 /*
1860  * Local variables:
1861  * tab-width: 4
1862  * c-basic-offset: 4
1863  * End:
1864  * vim600: noet sw=4 ts=4 fdm=marker
1865  * vim<600: noet sw=4 ts=4
1866  */
1867