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