/* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 2006-2011 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Authors: Georg Richter | | Andrey Hristov | | Ulf Wendel | +----------------------------------------------------------------------+ */ /* $Id$ */ #include "php.h" #include "mysqlnd.h" #include "mysqlnd_priv.h" #include "mysqlnd_debug.h" #include "mysqlnd_wireprotocol.h" #include "mysqlnd_statistics.h" #include "zend_builtin_functions.h" static const char * const mysqlnd_debug_default_trace_file = "/tmp/mysqlnd.trace"; #ifdef ZTS #define MYSQLND_ZTS(self) TSRMLS_D = (self)->TSRMLS_C #else #define MYSQLND_ZTS(self) #endif static const char mysqlnd_emalloc_name[] = "_mysqlnd_emalloc"; static const char mysqlnd_pemalloc_name[] = "_mysqlnd_pemalloc"; static const char mysqlnd_ecalloc_name[] = "_mysqlnd_ecalloc"; static const char mysqlnd_pecalloc_name[] = "_mysqlnd_pecalloc"; static const char mysqlnd_erealloc_name[] = "_mysqlnd_erealloc"; static const char mysqlnd_perealloc_name[] = "_mysqlnd_perealloc"; static const char mysqlnd_efree_name[] = "_mysqlnd_efree"; static const char mysqlnd_pefree_name[] = "_mysqlnd_pefree"; static const char mysqlnd_malloc_name[] = "_mysqlnd_malloc"; static const char mysqlnd_calloc_name[] = "_mysqlnd_calloc"; static const char mysqlnd_realloc_name[] = "_mysqlnd_realloc"; static const char mysqlnd_free_name[] = "_mysqlnd_free"; static const char mysqlnd_pestrndup_name[] = "_mysqlnd_pestrndup"; static const char mysqlnd_pestrdup_name[] = "_mysqlnd_pestrdup"; const char * mysqlnd_debug_std_no_trace_funcs[] = { mysqlnd_emalloc_name, mysqlnd_ecalloc_name, mysqlnd_efree_name, mysqlnd_erealloc_name, mysqlnd_pemalloc_name, mysqlnd_pecalloc_name, mysqlnd_pefree_name, mysqlnd_perealloc_name, mysqlnd_malloc_name, mysqlnd_calloc_name, mysqlnd_realloc_name, mysqlnd_free_name, mysqlnd_pestrndup_name, mysqlnd_read_header_name, mysqlnd_read_body_name, NULL /* must be always last */ }; /* {{{ mysqlnd_debug::open */ static enum_func_status MYSQLND_METHOD(mysqlnd_debug, open)(MYSQLND_DEBUG * self, zend_bool reopen) { MYSQLND_ZTS(self); if (!self->file_name) { return FAIL; } self->stream = php_stream_open_wrapper(self->file_name, reopen == TRUE || self->flags & MYSQLND_DEBUG_APPEND? "ab":"wb", REPORT_ERRORS, NULL); return self->stream? PASS:FAIL; } /* }}} */ /* {{{ mysqlnd_debug::log */ static enum_func_status MYSQLND_METHOD(mysqlnd_debug, log)(MYSQLND_DEBUG * self, unsigned int line, const char * const file, unsigned int level, const char * type, const char * message) { char pipe_buffer[512]; enum_func_status ret; int i; char * message_line; unsigned int message_line_len; unsigned int flags = self->flags; char pid_buffer[10], time_buffer[30], file_buffer[200], line_buffer[6], level_buffer[7]; MYSQLND_ZTS(self); if (!self->stream && FAIL == self->m->open(self, FALSE)) { return FAIL; } if (level == -1) { level = zend_stack_count(&self->call_stack); } i = MIN(level, sizeof(pipe_buffer) / 2 - 1); pipe_buffer[i*2] = '\0'; for (;i > 0;i--) { pipe_buffer[i*2 - 1] = ' '; pipe_buffer[i*2 - 2] = '|'; } if (flags & MYSQLND_DEBUG_DUMP_PID) { snprintf(pid_buffer, sizeof(pid_buffer) - 1, "%5u: ", self->pid); pid_buffer[sizeof(pid_buffer) - 1 ] = '\0'; } if (flags & MYSQLND_DEBUG_DUMP_TIME) { /* The following from FF's DBUG library, which is in the public domain */ #if defined(PHP_WIN32) /* FIXME This doesn't give microseconds as in Unix case, and the resolution is in system ticks, 10 ms intervals. See my_getsystime.c for high res */ SYSTEMTIME loc_t; GetLocalTime(&loc_t); snprintf(time_buffer, sizeof(time_buffer) - 1, /* "%04d-%02d-%02d " */ "%02d:%02d:%02d.%06d ", /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/ loc_t.wHour, loc_t.wMinute, loc_t.wSecond, loc_t.wMilliseconds); time_buffer[sizeof(time_buffer) - 1 ] = '\0'; #else struct timeval tv; struct tm *tm_p; if (gettimeofday(&tv, NULL) != -1) { if ((tm_p= localtime((const time_t *)&tv.tv_sec))) { snprintf(time_buffer, sizeof(time_buffer) - 1, /* "%04d-%02d-%02d " */ "%02d:%02d:%02d.%06d ", /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/ tm_p->tm_hour, tm_p->tm_min, tm_p->tm_sec, (int) (tv.tv_usec)); time_buffer[sizeof(time_buffer) - 1 ] = '\0'; } } #endif } if (flags & MYSQLND_DEBUG_DUMP_FILE) { snprintf(file_buffer, sizeof(file_buffer) - 1, "%14s: ", file); file_buffer[sizeof(file_buffer) - 1 ] = '\0'; } if (flags & MYSQLND_DEBUG_DUMP_LINE) { snprintf(line_buffer, sizeof(line_buffer) - 1, "%5u: ", line); line_buffer[sizeof(line_buffer) - 1 ] = '\0'; } if (flags & MYSQLND_DEBUG_DUMP_LEVEL) { snprintf(level_buffer, sizeof(level_buffer) - 1, "%4u: ", level); level_buffer[sizeof(level_buffer) - 1 ] = '\0'; } message_line_len = spprintf(&message_line, 0, "%s%s%s%s%s%s%s%s\n", flags & MYSQLND_DEBUG_DUMP_PID? pid_buffer:"", flags & MYSQLND_DEBUG_DUMP_TIME? time_buffer:"", flags & MYSQLND_DEBUG_DUMP_FILE? file_buffer:"", flags & MYSQLND_DEBUG_DUMP_LINE? line_buffer:"", flags & MYSQLND_DEBUG_DUMP_LEVEL? level_buffer:"", pipe_buffer, type? type:"", message); ret = php_stream_write(self->stream, message_line, message_line_len)? PASS:FAIL; efree(message_line); /* allocated by spprintf */ if (flags & MYSQLND_DEBUG_FLUSH) { self->m->close(self); self->m->open(self, TRUE); } return ret; } /* }}} */ /* {{{ mysqlnd_debug::log_va */ static enum_func_status MYSQLND_METHOD(mysqlnd_debug, log_va)(MYSQLND_DEBUG *self, unsigned int line, const char * const file, unsigned int level, const char * type, const char *format, ...) { char pipe_buffer[512]; int i; enum_func_status ret; char * message_line, *buffer; unsigned int message_line_len; va_list args; unsigned int flags = self->flags; char pid_buffer[10], time_buffer[30], file_buffer[200], line_buffer[6], level_buffer[7]; MYSQLND_ZTS(self); if (!self->stream && FAIL == self->m->open(self, FALSE)) { return FAIL; } if (level == -1) { level = zend_stack_count(&self->call_stack); } i = MIN(level, sizeof(pipe_buffer) / 2 - 1); pipe_buffer[i*2] = '\0'; for (;i > 0;i--) { pipe_buffer[i*2 - 1] = ' '; pipe_buffer[i*2 - 2] = '|'; } if (flags & MYSQLND_DEBUG_DUMP_PID) { snprintf(pid_buffer, sizeof(pid_buffer) - 1, "%5u: ", self->pid); pid_buffer[sizeof(pid_buffer) - 1 ] = '\0'; } if (flags & MYSQLND_DEBUG_DUMP_TIME) { /* The following from FF's DBUG library, which is in the public domain */ #if defined(PHP_WIN32) /* FIXME This doesn't give microseconds as in Unix case, and the resolution is in system ticks, 10 ms intervals. See my_getsystime.c for high res */ SYSTEMTIME loc_t; GetLocalTime(&loc_t); snprintf(time_buffer, sizeof(time_buffer) - 1, /* "%04d-%02d-%02d " */ "%02d:%02d:%02d.%06d ", /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/ loc_t.wHour, loc_t.wMinute, loc_t.wSecond, loc_t.wMilliseconds); time_buffer[sizeof(time_buffer) - 1 ] = '\0'; #else struct timeval tv; struct tm *tm_p; if (gettimeofday(&tv, NULL) != -1) { if ((tm_p= localtime((const time_t *)&tv.tv_sec))) { snprintf(time_buffer, sizeof(time_buffer) - 1, /* "%04d-%02d-%02d " */ "%02d:%02d:%02d.%06d ", /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/ tm_p->tm_hour, tm_p->tm_min, tm_p->tm_sec, (int) (tv.tv_usec)); time_buffer[sizeof(time_buffer) - 1 ] = '\0'; } } #endif } if (flags & MYSQLND_DEBUG_DUMP_FILE) { snprintf(file_buffer, sizeof(file_buffer) - 1, "%14s: ", file); file_buffer[sizeof(file_buffer) - 1 ] = '\0'; } if (flags & MYSQLND_DEBUG_DUMP_LINE) { snprintf(line_buffer, sizeof(line_buffer) - 1, "%5u: ", line); line_buffer[sizeof(line_buffer) - 1 ] = '\0'; } if (flags & MYSQLND_DEBUG_DUMP_LEVEL) { snprintf(level_buffer, sizeof(level_buffer) - 1, "%4u: ", level); level_buffer[sizeof(level_buffer) - 1 ] = '\0'; } va_start(args, format); vspprintf(&buffer, 0, format, args); va_end(args); message_line_len = spprintf(&message_line, 0, "%s%s%s%s%s%s%s%s\n", flags & MYSQLND_DEBUG_DUMP_PID? pid_buffer:"", flags & MYSQLND_DEBUG_DUMP_TIME? time_buffer:"", flags & MYSQLND_DEBUG_DUMP_FILE? file_buffer:"", flags & MYSQLND_DEBUG_DUMP_LINE? line_buffer:"", flags & MYSQLND_DEBUG_DUMP_LEVEL? level_buffer:"", pipe_buffer, type? type:"", buffer); efree(buffer); ret = php_stream_write(self->stream, message_line, message_line_len)? PASS:FAIL; efree(message_line); /* allocated by spprintf */ if (flags & MYSQLND_DEBUG_FLUSH) { self->m->close(self); self->m->open(self, TRUE); } return ret; } /* }}} */ /* FALSE - The DBG_ calls won't be traced, TRUE - will be traced */ /* {{{ mysqlnd_debug::func_enter */ static zend_bool MYSQLND_METHOD(mysqlnd_debug, func_enter)(MYSQLND_DEBUG * self, unsigned int line, const char * const file, const char * const func_name, unsigned int func_name_len) { if ((self->flags & MYSQLND_DEBUG_DUMP_TRACE) == 0 || self->file_name == NULL) { return FALSE; } if ((uint) zend_stack_count(&self->call_stack) >= self->nest_level_limit) { return FALSE; } if ((self->flags & MYSQLND_DEBUG_TRACE_MEMORY_CALLS) == 0 && self->skip_functions) { const char ** p = self->skip_functions; while (*p) { if (*p == func_name) { zend_stack_push(&self->call_stack, "", sizeof("")); #ifndef MYSQLND_PROFILING_DISABLED if (self->flags & MYSQLND_DEBUG_PROFILE_CALLS) { uint64_t some_time = 0; zend_stack_push(&self->call_time_stack, &some_time, sizeof(some_time)); } #endif return FALSE; } p++; } } zend_stack_push(&self->call_stack, func_name, func_name_len + 1); #ifndef MYSQLND_PROFILING_DISABLED if (self->flags & MYSQLND_DEBUG_PROFILE_CALLS) { uint64_t some_time = 0; zend_stack_push(&self->call_time_stack, &some_time, sizeof(some_time)); } #endif if (zend_hash_num_elements(&self->not_filtered_functions) && 0 == zend_hash_exists(&self->not_filtered_functions, func_name, strlen(func_name) + 1)) { return FALSE; } self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, ">%s", func_name); return TRUE; } /* }}} */ #ifndef MYSQLND_PROFILING_DISABLED struct st_mysqlnd_dbg_function_profile { uint64_t calls; uint64_t min_own; uint64_t max_own; uint64_t avg_own; uint64_t own_underporm_calls; uint64_t min_in_calls; uint64_t max_in_calls; uint64_t avg_in_calls; uint64_t in_calls_underporm_calls; uint64_t min_total; uint64_t max_total; uint64_t avg_total; uint64_t total_underporm_calls; }; #define PROFILE_UNDERPERFORM_THRESHOLD 10 #endif /* {{{ mysqlnd_debug::func_leave */ static enum_func_status MYSQLND_METHOD(mysqlnd_debug, func_leave)(MYSQLND_DEBUG * self, unsigned int line, const char * const file, uint64_t call_time) { char *func_name; uint64_t * parent_non_own_time_ptr = NULL, * mine_non_own_time_ptr = NULL; uint64_t mine_non_own_time = 0; zend_bool profile_calls = self->flags & MYSQLND_DEBUG_PROFILE_CALLS? TRUE:FALSE; if ((self->flags & MYSQLND_DEBUG_DUMP_TRACE) == 0 || self->file_name == NULL) { return PASS; } if ((uint) zend_stack_count(&self->call_stack) >= self->nest_level_limit) { return PASS; } zend_stack_top(&self->call_stack, (void **)&func_name); #ifndef MYSQLND_PROFILING_DISABLED if (profile_calls) { zend_stack_top(&self->call_time_stack, (void **)&mine_non_own_time_ptr); mine_non_own_time = *mine_non_own_time_ptr; zend_stack_del_top(&self->call_time_stack); /* callee - removing ourselves */ } #endif if (func_name[0] == '\0') { ; /* don't log that function */ } else if (!zend_hash_num_elements(&self->not_filtered_functions) || 1 == zend_hash_exists(&self->not_filtered_functions, func_name, strlen(func_name) + 1)) { #ifndef MYSQLND_PROFILING_DISABLED if (FALSE == profile_calls) { #endif self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, "<%s", func_name); #ifndef MYSQLND_PROFILING_DISABLED } else { struct st_mysqlnd_dbg_function_profile f_profile_stack = {0}; struct st_mysqlnd_dbg_function_profile * f_profile = NULL; uint64_t own_time = call_time - mine_non_own_time; uint func_name_len = strlen(func_name); self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, "<%s (total=%u own=%u in_calls=%u)", func_name, (unsigned int) call_time, (unsigned int) own_time, (unsigned int) mine_non_own_time ); if (SUCCESS == zend_hash_find(&self->function_profiles, func_name, func_name_len + 1, (void **) &f_profile)) { /* found */ if (f_profile) { if (mine_non_own_time < f_profile->min_in_calls) { f_profile->min_in_calls = mine_non_own_time; } else if (mine_non_own_time > f_profile->max_in_calls) { f_profile->max_in_calls = mine_non_own_time; } f_profile->avg_in_calls = (f_profile->avg_in_calls * f_profile->calls + mine_non_own_time) / (f_profile->calls + 1); if (own_time < f_profile->min_own) { f_profile->min_own = own_time; } else if (own_time > f_profile->max_own) { f_profile->max_own = own_time; } f_profile->avg_own = (f_profile->avg_own * f_profile->calls + own_time) / (f_profile->calls + 1); if (call_time < f_profile->min_total) { f_profile->min_total = call_time; } else if (call_time > f_profile->max_total) { f_profile->max_total = call_time; } f_profile->avg_total = (f_profile->avg_total * f_profile->calls + call_time) / (f_profile->calls + 1); ++f_profile->calls; if (f_profile->calls > PROFILE_UNDERPERFORM_THRESHOLD) { if (f_profile->avg_in_calls < mine_non_own_time) { f_profile->in_calls_underporm_calls++; } if (f_profile->avg_own < own_time) { f_profile->own_underporm_calls++; } if (f_profile->avg_total < call_time) { f_profile->total_underporm_calls++; } } } } else { /* add */ f_profile = &f_profile_stack; f_profile->min_in_calls = f_profile->max_in_calls = f_profile->avg_in_calls = mine_non_own_time; f_profile->min_total = f_profile->max_total = f_profile->avg_total = call_time; f_profile->min_own = f_profile->max_own = f_profile->avg_own = own_time; f_profile->calls = 1; zend_hash_add(&self->function_profiles, func_name, func_name_len+1, f_profile, sizeof(struct st_mysqlnd_dbg_function_profile), NULL); } if ((uint) zend_stack_count(&self->call_time_stack)) { uint64_t parent_non_own_time = 0; zend_stack_top(&self->call_time_stack, (void **)&parent_non_own_time_ptr); parent_non_own_time = *parent_non_own_time_ptr; parent_non_own_time += call_time; zend_stack_del_top(&self->call_time_stack); /* the caller */ zend_stack_push(&self->call_time_stack, &parent_non_own_time, sizeof(parent_non_own_time)); /* add back the caller */ } } #endif } return zend_stack_del_top(&self->call_stack) == SUCCESS? PASS:FAIL; } /* }}} */ /* {{{ mysqlnd_debug::close */ static enum_func_status MYSQLND_METHOD(mysqlnd_debug, close)(MYSQLND_DEBUG * self) { MYSQLND_ZTS(self); if (self->stream) { #ifndef MYSQLND_PROFILING_DISABLED if (!(self->flags & MYSQLND_DEBUG_FLUSH) && (self->flags & MYSQLND_DEBUG_PROFILE_CALLS)) { struct st_mysqlnd_dbg_function_profile * f_profile; HashPosition pos_values; self->m->log_va(self, __LINE__, __FILE__, 0, "info : ", "number of functions: %d", zend_hash_num_elements(&self->function_profiles)); zend_hash_internal_pointer_reset_ex(&self->function_profiles, &pos_values); while (zend_hash_get_current_data_ex(&self->function_profiles, (void **) &f_profile, &pos_values) == SUCCESS) { char *string_key = NULL; uint string_key_len; ulong num_key; zend_hash_get_current_key_ex(&self->function_profiles, &string_key, &string_key_len, &num_key, 0, &pos_values); self->m->log_va(self, __LINE__, __FILE__, -1, "info : ", "%-40s\tcalls=%5llu own_slow=%5llu in_calls_slow=%5llu total_slow=%5llu" " min_own=%5llu max_own=%7llu avg_own=%7llu " " min_in_calls=%5llu max_in_calls=%7llu avg_in_calls=%7llu" " min_total=%5llu max_total=%7llu avg_total=%7llu" ,string_key ,(uint64_t) f_profile->calls ,(uint64_t) f_profile->own_underporm_calls ,(uint64_t) f_profile->in_calls_underporm_calls ,(uint64_t) f_profile->total_underporm_calls ,(uint64_t) f_profile->min_own ,(uint64_t) f_profile->max_own ,(uint64_t) f_profile->avg_own ,(uint64_t) f_profile->min_in_calls ,(uint64_t) f_profile->max_in_calls ,(uint64_t) f_profile->avg_in_calls ,(uint64_t) f_profile->min_total ,(uint64_t) f_profile->max_total ,(uint64_t) f_profile->avg_total ); zend_hash_move_forward_ex(&self->function_profiles, &pos_values); } } #endif php_stream_free(self->stream, PHP_STREAM_FREE_CLOSE); self->stream = NULL; } /* no DBG_RETURN please */ return PASS; } /* }}} */ /* {{{ mysqlnd_res_meta::free */ static enum_func_status MYSQLND_METHOD(mysqlnd_debug, free)(MYSQLND_DEBUG * self) { if (self->file_name && self->file_name != mysqlnd_debug_default_trace_file) { efree(self->file_name); self->file_name = NULL; } zend_stack_destroy(&self->call_stack); zend_stack_destroy(&self->call_time_stack); zend_hash_destroy(&self->not_filtered_functions); zend_hash_destroy(&self->function_profiles); efree(self); return PASS; } /* }}} */ enum mysqlnd_debug_parser_state { PARSER_WAIT_MODIFIER, PARSER_WAIT_COLON, PARSER_WAIT_VALUE }; /* {{{ mysqlnd_res_meta::set_mode */ static void MYSQLND_METHOD(mysqlnd_debug, set_mode)(MYSQLND_DEBUG * self, const char * const mode) { unsigned int mode_len = strlen(mode), i; enum mysqlnd_debug_parser_state state = PARSER_WAIT_MODIFIER; self->flags = 0; self->nest_level_limit = 0; if (self->file_name && self->file_name != mysqlnd_debug_default_trace_file) { efree(self->file_name); self->file_name = NULL; } if (zend_hash_num_elements(&self->not_filtered_functions)) { zend_hash_destroy(&self->not_filtered_functions); zend_hash_init(&self->not_filtered_functions, 0, NULL, NULL, 0); } for (i = 0; i < mode_len; i++) { switch (mode[i]) { case 'O': case 'A': self->flags |= MYSQLND_DEBUG_FLUSH; case 'a': case 'o': if (mode[i] == 'a' || mode[i] == 'A') { self->flags |= MYSQLND_DEBUG_APPEND; } if (i + 1 < mode_len && mode[i+1] == ',') { unsigned int j = i + 2; #ifdef PHP_WIN32 if (i+4 < mode_len && mode[i+3] == ':' && (mode[i+4] == '\\' || mode[i+5] == '/')) { j = i + 5; } #endif while (j < mode_len) { if (mode[j] == ':') { break; } j++; } if (j > i + 2) { self->file_name = estrndup(mode + i + 2, j - i - 2); } i = j; } else { if (!self->file_name) self->file_name = (char *) mysqlnd_debug_default_trace_file; } state = PARSER_WAIT_COLON; break; case ':': #if 0 if (state != PARSER_WAIT_COLON) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Consecutive semicolons at position %u", i); } #endif state = PARSER_WAIT_MODIFIER; break; case 'f': /* limit output to these functions */ if (i + 1 < mode_len && mode[i+1] == ',') { unsigned int j = i + 2; i++; while (j < mode_len) { if (mode[j] == ':') { /* function names with :: */ if ((j + 1 < mode_len) && mode[j+1] == ':') { j += 2; continue; } } if (mode[j] == ',' || mode[j] == ':') { if (j > i + 2) { char func_name[1024]; unsigned int func_name_len = MIN(sizeof(func_name) - 1, j - i - 1); memcpy(func_name, mode + i + 1, func_name_len); func_name[func_name_len] = '\0'; zend_hash_add_empty_element(&self->not_filtered_functions, func_name, func_name_len + 1); i = j; } if (mode[j] == ':') { break; } } j++; } i = j; } else { #if 0 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Expected list of functions for '%c' found none", mode[i]); #endif } state = PARSER_WAIT_COLON; break; case 'D': case 'd': case 'g': case 'p': /* unsupported */ if ((i + 1) < mode_len && mode[i+1] == ',') { i+= 2; while (i < mode_len) { if (mode[i] == ':') { break; } i++; } } state = PARSER_WAIT_COLON; break; case 'F': self->flags |= MYSQLND_DEBUG_DUMP_FILE; state = PARSER_WAIT_COLON; break; case 'i': self->flags |= MYSQLND_DEBUG_DUMP_PID; state = PARSER_WAIT_COLON; break; case 'L': self->flags |= MYSQLND_DEBUG_DUMP_LINE; state = PARSER_WAIT_COLON; break; case 'n': self->flags |= MYSQLND_DEBUG_DUMP_LEVEL; state = PARSER_WAIT_COLON; break; case 't': if (mode[i+1] == ',') { unsigned int j = i + 2; while (j < mode_len) { if (mode[j] == ':') { break; } j++; } if (j > i + 2) { char *value_str = estrndup(mode + i + 2, j - i - 2); self->nest_level_limit = atoi(value_str); efree(value_str); } i = j; } else { self->nest_level_limit = 200; /* default value for FF DBUG */ } self->flags |= MYSQLND_DEBUG_DUMP_TRACE; state = PARSER_WAIT_COLON; break; case 'T': self->flags |= MYSQLND_DEBUG_DUMP_TIME; state = PARSER_WAIT_COLON; break; case 'N': case 'P': case 'r': case 'S': state = PARSER_WAIT_COLON; break; case 'm': /* mysqlnd extension - trace memory functions */ self->flags |= MYSQLND_DEBUG_TRACE_MEMORY_CALLS; state = PARSER_WAIT_COLON; break; case 'x': /* mysqlnd extension - profile calls */ self->flags |= MYSQLND_DEBUG_PROFILE_CALLS; state = PARSER_WAIT_COLON; break; default: if (state == PARSER_WAIT_MODIFIER) { #if 0 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unrecognized format '%c'", mode[i]); #endif if (i+1 < mode_len && mode[i+1] == ',') { i+= 2; while (i < mode_len) { if (mode[i] == ':') { break; } i++; } } state = PARSER_WAIT_COLON; } else if (state == PARSER_WAIT_COLON) { #if 0 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Colon expected, '%c' found", mode[i]); #endif } break; } } } /* }}} */ MYSQLND_CLASS_METHODS_START(mysqlnd_debug) MYSQLND_METHOD(mysqlnd_debug, open), MYSQLND_METHOD(mysqlnd_debug, set_mode), MYSQLND_METHOD(mysqlnd_debug, log), MYSQLND_METHOD(mysqlnd_debug, log_va), MYSQLND_METHOD(mysqlnd_debug, func_enter), MYSQLND_METHOD(mysqlnd_debug, func_leave), MYSQLND_METHOD(mysqlnd_debug, close), MYSQLND_METHOD(mysqlnd_debug, free), MYSQLND_CLASS_METHODS_END; /* {{{ mysqlnd_debug_init */ PHPAPI MYSQLND_DEBUG * mysqlnd_debug_init(const char * skip_functions[] TSRMLS_DC) { MYSQLND_DEBUG *ret = ecalloc(1, sizeof(MYSQLND_DEBUG)); #ifdef ZTS ret->TSRMLS_C = TSRMLS_C; #endif ret->nest_level_limit = 0; ret->pid = getpid(); zend_stack_init(&ret->call_stack); zend_stack_init(&ret->call_time_stack); zend_hash_init(&ret->not_filtered_functions, 0, NULL, NULL, 0); zend_hash_init(&ret->function_profiles, 0, NULL, NULL, 0); ret->m = & mysqlnd_mysqlnd_debug_methods; ret->skip_functions = skip_functions; return ret; } /* }}} */ /* {{{ _mysqlnd_debug */ PHPAPI void _mysqlnd_debug(const char * mode TSRMLS_DC) { #if PHP_DEBUG MYSQLND_DEBUG *dbg = MYSQLND_G(dbg); if (!dbg) { MYSQLND_G(dbg) = dbg = mysqlnd_debug_init(mysqlnd_debug_std_no_trace_funcs TSRMLS_CC); if (!dbg) { return; } } dbg->m->close(dbg); dbg->m->set_mode(dbg, mode); while (zend_stack_count(&dbg->call_stack)) { zend_stack_del_top(&dbg->call_stack); } while (zend_stack_count(&dbg->call_time_stack)) { zend_stack_del_top(&dbg->call_time_stack); } #endif } /* }}} */ #if ZEND_DEBUG #else #define __zend_filename "/unknown/unknown" #define __zend_lineno 0 #endif #define REAL_SIZE(s) (collect_memory_statistics? (s) + sizeof(size_t) : (s)) #define REAL_PTR(p) (collect_memory_statistics && (p)? (((char *)(p)) - sizeof(size_t)) : (p)) #define FAKE_PTR(p) (collect_memory_statistics && (p)? (((char *)(p)) + sizeof(size_t)) : (p)) /* {{{ _mysqlnd_emalloc */ void * _mysqlnd_emalloc(size_t size MYSQLND_MEM_D) { void *ret; zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); long * threshold = &MYSQLND_G(debug_emalloc_fail_threshold); DBG_ENTER(mysqlnd_emalloc_name); DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); #if PHP_DEBUG /* -1 is also "true" */ if (*threshold) { #endif ret = emalloc(REAL_SIZE(size)); #if PHP_DEBUG --*threshold; } else if (*threshold == 0) { ret = NULL; } #endif DBG_INF_FMT("size=%lu ptr=%p", size, ret); if (ret && collect_memory_statistics) { *(size_t *) ret = size; MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_EMALLOC_COUNT, 1, STAT_MEM_EMALLOC_AMOUNT, size); } DBG_RETURN(FAKE_PTR(ret)); } /* }}} */ /* {{{ _mysqlnd_pemalloc */ void * _mysqlnd_pemalloc(size_t size, zend_bool persistent MYSQLND_MEM_D) { void *ret; zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); long * threshold = persistent? &MYSQLND_G(debug_malloc_fail_threshold):&MYSQLND_G(debug_emalloc_fail_threshold); DBG_ENTER(mysqlnd_pemalloc_name); DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); #if PHP_DEBUG /* -1 is also "true" */ if (*threshold) { #endif ret = pemalloc(REAL_SIZE(size), persistent); #if PHP_DEBUG --*threshold; } else if (*threshold == 0) { ret = NULL; } #endif DBG_INF_FMT("size=%lu ptr=%p persistent=%u", size, ret, persistent); if (ret && collect_memory_statistics) { enum mysqlnd_collected_stats s1 = persistent? STAT_MEM_MALLOC_COUNT:STAT_MEM_EMALLOC_COUNT; enum mysqlnd_collected_stats s2 = persistent? STAT_MEM_MALLOC_AMOUNT:STAT_MEM_EMALLOC_AMOUNT; *(size_t *) ret = size; MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(s1, 1, s2, size); } DBG_RETURN(FAKE_PTR(ret)); } /* }}} */ /* {{{ _mysqlnd_ecalloc */ void * _mysqlnd_ecalloc(unsigned int nmemb, size_t size MYSQLND_MEM_D) { void *ret; zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); long * threshold = &MYSQLND_G(debug_ecalloc_fail_threshold); DBG_ENTER(mysqlnd_ecalloc_name); DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); DBG_INF_FMT("before: %lu", zend_memory_usage(FALSE TSRMLS_CC)); #if PHP_DEBUG /* -1 is also "true" */ if (*threshold) { #endif ret = ecalloc(nmemb, REAL_SIZE(size)); #if PHP_DEBUG --*threshold; } else if (*threshold == 0) { ret = NULL; } #endif DBG_INF_FMT("after : %lu", zend_memory_usage(FALSE TSRMLS_CC)); DBG_INF_FMT("size=%lu ptr=%p", size, ret); if (ret && collect_memory_statistics) { *(size_t *) ret = size; MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_ECALLOC_COUNT, 1, STAT_MEM_ECALLOC_AMOUNT, size); } DBG_RETURN(FAKE_PTR(ret)); } /* }}} */ /* {{{ _mysqlnd_pecalloc */ void * _mysqlnd_pecalloc(unsigned int nmemb, size_t size, zend_bool persistent MYSQLND_MEM_D) { void *ret; zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); long * threshold = persistent? &MYSQLND_G(debug_calloc_fail_threshold):&MYSQLND_G(debug_ecalloc_fail_threshold); DBG_ENTER(mysqlnd_pecalloc_name); DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); #if PHP_DEBUG /* -1 is also "true" */ if (*threshold) { #endif ret = pecalloc(nmemb, REAL_SIZE(size), persistent); #if PHP_DEBUG --*threshold; } else if (*threshold == 0) { ret = NULL; } #endif DBG_INF_FMT("size=%lu ptr=%p", size, ret); if (ret && collect_memory_statistics) { enum mysqlnd_collected_stats s1 = persistent? STAT_MEM_CALLOC_COUNT:STAT_MEM_ECALLOC_COUNT; enum mysqlnd_collected_stats s2 = persistent? STAT_MEM_CALLOC_AMOUNT:STAT_MEM_ECALLOC_AMOUNT; *(size_t *) ret = size; MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(s1, 1, s2, size); } DBG_RETURN(FAKE_PTR(ret)); } /* }}} */ /* {{{ _mysqlnd_erealloc */ void * _mysqlnd_erealloc(void *ptr, size_t new_size MYSQLND_MEM_D) { void *ret; zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); size_t old_size = collect_memory_statistics && ptr? *(size_t *) (((char*)ptr) - sizeof(size_t)) : 0; long * threshold = &MYSQLND_G(debug_erealloc_fail_threshold); DBG_ENTER(mysqlnd_erealloc_name); DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); DBG_INF_FMT("ptr=%p old_size=%lu, new_size=%lu", ptr, old_size, new_size); #if PHP_DEBUG /* -1 is also "true" */ if (*threshold) { #endif ret = erealloc(REAL_PTR(ptr), REAL_SIZE(new_size)); #if PHP_DEBUG --*threshold; } else if (*threshold == 0) { ret = NULL; } #endif DBG_INF_FMT("new_ptr=%p", (char*)ret); if (ret && collect_memory_statistics) { *(size_t *) ret = new_size; MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_EREALLOC_COUNT, 1, STAT_MEM_EREALLOC_AMOUNT, new_size); } DBG_RETURN(FAKE_PTR(ret)); } /* }}} */ /* {{{ _mysqlnd_perealloc */ void * _mysqlnd_perealloc(void *ptr, size_t new_size, zend_bool persistent MYSQLND_MEM_D) { void *ret; zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); size_t old_size = collect_memory_statistics && ptr? *(size_t *) (((char*)ptr) - sizeof(size_t)) : 0; long * threshold = persistent? &MYSQLND_G(debug_realloc_fail_threshold):&MYSQLND_G(debug_erealloc_fail_threshold); DBG_ENTER(mysqlnd_perealloc_name); DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); DBG_INF_FMT("ptr=%p old_size=%lu new_size=%lu persistent=%u", ptr, old_size, new_size, persistent); #if PHP_DEBUG /* -1 is also "true" */ if (*threshold) { #endif ret = perealloc(REAL_PTR(ptr), REAL_SIZE(new_size), persistent); #if PHP_DEBUG --*threshold; } else if (*threshold == 0) { ret = NULL; } #endif DBG_INF_FMT("new_ptr=%p", (char*)ret); if (ret && collect_memory_statistics) { enum mysqlnd_collected_stats s1 = persistent? STAT_MEM_REALLOC_COUNT:STAT_MEM_EREALLOC_COUNT; enum mysqlnd_collected_stats s2 = persistent? STAT_MEM_REALLOC_AMOUNT:STAT_MEM_EREALLOC_AMOUNT; *(size_t *) ret = new_size; MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(s1, 1, s2, new_size); } DBG_RETURN(FAKE_PTR(ret)); } /* }}} */ /* {{{ _mysqlnd_efree */ void _mysqlnd_efree(void *ptr MYSQLND_MEM_D) { size_t free_amount = 0; zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); DBG_ENTER(mysqlnd_efree_name); DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); DBG_INF_FMT("ptr=%p", ptr); if (ptr) { if (collect_memory_statistics) { free_amount = *(size_t *)(((char*)ptr) - sizeof(size_t)); DBG_INF_FMT("ptr=%p size=%u", ((char*)ptr) - sizeof(size_t), (unsigned int) free_amount); } efree(REAL_PTR(ptr)); } if (collect_memory_statistics) { MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_EFREE_COUNT, 1, STAT_MEM_EFREE_AMOUNT, free_amount); } DBG_VOID_RETURN; } /* }}} */ /* {{{ _mysqlnd_pefree */ void _mysqlnd_pefree(void *ptr, zend_bool persistent MYSQLND_MEM_D) { size_t free_amount = 0; zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); DBG_ENTER(mysqlnd_pefree_name); DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); DBG_INF_FMT("ptr=%p persistent=%u", ptr, persistent); if (ptr) { if (collect_memory_statistics) { free_amount = *(size_t *)(((char*)ptr) - sizeof(size_t)); DBG_INF_FMT("ptr=%p size=%u", ((char*)ptr) - sizeof(size_t), (unsigned int) free_amount); } pefree(REAL_PTR(ptr), persistent); } if (collect_memory_statistics) { MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(persistent? STAT_MEM_FREE_COUNT:STAT_MEM_EFREE_COUNT, 1, persistent? STAT_MEM_FREE_AMOUNT:STAT_MEM_EFREE_AMOUNT, free_amount); } DBG_VOID_RETURN; } /* {{{ _mysqlnd_malloc */ void * _mysqlnd_malloc(size_t size MYSQLND_MEM_D) { void *ret; zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); long * threshold = &MYSQLND_G(debug_malloc_fail_threshold); DBG_ENTER(mysqlnd_malloc_name); DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); #if PHP_DEBUG /* -1 is also "true" */ if (*threshold) { #endif ret = malloc(REAL_SIZE(size)); #if PHP_DEBUG --*threshold; } else if (*threshold == 0) { ret = NULL; } #endif DBG_INF_FMT("size=%lu ptr=%p", size, ret); if (ret && collect_memory_statistics) { *(size_t *) ret = size; MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_MALLOC_COUNT, 1, STAT_MEM_MALLOC_AMOUNT, size); } DBG_RETURN(FAKE_PTR(ret)); } /* }}} */ /* {{{ _mysqlnd_calloc */ void * _mysqlnd_calloc(unsigned int nmemb, size_t size MYSQLND_MEM_D) { void *ret; zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); long * threshold = &MYSQLND_G(debug_calloc_fail_threshold); DBG_ENTER(mysqlnd_calloc_name); DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); #if PHP_DEBUG /* -1 is also "true" */ if (*threshold) { #endif ret = calloc(nmemb, REAL_SIZE(size)); #if PHP_DEBUG --*threshold; } else if (*threshold == 0) { ret = NULL; } #endif DBG_INF_FMT("size=%lu ptr=%p", size, ret); if (ret && collect_memory_statistics) { *(size_t *) ret = size; MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_CALLOC_COUNT, 1, STAT_MEM_CALLOC_AMOUNT, size); } DBG_RETURN(FAKE_PTR(ret)); } /* }}} */ /* {{{ _mysqlnd_realloc */ void * _mysqlnd_realloc(void *ptr, size_t new_size MYSQLND_MEM_D) { void *ret; zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); long * threshold = &MYSQLND_G(debug_realloc_fail_threshold); DBG_ENTER(mysqlnd_realloc_name); DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); DBG_INF_FMT("ptr=%p new_size=%lu ", new_size, ptr); DBG_INF_FMT("before: %lu", zend_memory_usage(TRUE TSRMLS_CC)); #if PHP_DEBUG /* -1 is also "true" */ if (*threshold) { #endif ret = realloc(REAL_PTR(ptr), REAL_SIZE(new_size)); #if PHP_DEBUG --*threshold; } else if (*threshold == 0) { ret = NULL; } #endif DBG_INF_FMT("new_ptr=%p", (char*)ret); if (ret && collect_memory_statistics) { *(size_t *) ret = new_size; MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_REALLOC_COUNT, 1, STAT_MEM_REALLOC_AMOUNT, new_size); } DBG_RETURN(FAKE_PTR(ret)); } /* }}} */ /* {{{ _mysqlnd_free */ void _mysqlnd_free(void *ptr MYSQLND_MEM_D) { size_t free_amount = 0; zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); DBG_ENTER(mysqlnd_free_name); DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); DBG_INF_FMT("ptr=%p", ptr); if (ptr) { if (collect_memory_statistics) { free_amount = *(size_t *)(((char*)ptr) - sizeof(size_t)); DBG_INF_FMT("ptr=%p size=%u", ((char*)ptr) - sizeof(size_t), (unsigned int) free_amount); } free(REAL_PTR(ptr)); } if (collect_memory_statistics) { MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_FREE_COUNT, 1, STAT_MEM_FREE_AMOUNT, free_amount); } DBG_VOID_RETURN; } /* }}} */ #define SMART_STR_START_SIZE 2048 #define SMART_STR_PREALLOC 512 #include "ext/standard/php_smart_str.h" /* {{{ _mysqlnd_pestrndup */ char * _mysqlnd_pestrndup(const char * const ptr, size_t length, zend_bool persistent MYSQLND_MEM_D) { char * ret; zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); DBG_ENTER(mysqlnd_pestrndup_name); DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); DBG_INF_FMT("ptr=%p", ptr); ret = pemalloc(REAL_SIZE(length) + 1, persistent); { size_t l = length; char * p = (char *) ptr; char * dest = (char *) FAKE_PTR(ret); while (*p && l--) { *dest++ = *p++; } *dest = '\0'; } if (collect_memory_statistics) { *(size_t *) ret = length; MYSQLND_INC_GLOBAL_STATISTIC(persistent? STAT_MEM_STRNDUP_COUNT : STAT_MEM_ESTRNDUP_COUNT); } DBG_RETURN(FAKE_PTR(ret)); } /* }}} */ /* {{{ _mysqlnd_pestrdup */ char * _mysqlnd_pestrdup(const char * const ptr, zend_bool persistent MYSQLND_MEM_D) { char * ret; smart_str tmp_str = {0, 0, 0}; const char * p = ptr; zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); DBG_ENTER(mysqlnd_pestrdup_name); DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); DBG_INF_FMT("ptr=%p", ptr); do { smart_str_appendc(&tmp_str, *p); } while (*p++); ret = pemalloc(tmp_str.len + sizeof(size_t), persistent); memcpy(FAKE_PTR(ret), tmp_str.c, tmp_str.len); if (ret && collect_memory_statistics) { *(size_t *) ret = tmp_str.len; MYSQLND_INC_GLOBAL_STATISTIC(persistent? STAT_MEM_STRDUP_COUNT : STAT_MEM_ESTRDUP_COUNT); } smart_str_free(&tmp_str); DBG_RETURN(FAKE_PTR(ret)); } /* }}} */ #define MYSQLND_DEBUG_MEMORY 1 #if MYSQLND_DEBUG_MEMORY == 0 /* {{{ mysqlnd_zend_mm_emalloc */ static void * mysqlnd_zend_mm_emalloc(size_t size MYSQLND_MEM_D) { return emalloc(size); } /* }}} */ /* {{{ mysqlnd_zend_mm_pemalloc */ static void * mysqlnd_zend_mm_pemalloc(size_t size, zend_bool persistent MYSQLND_MEM_D) { return pemalloc(size, persistent); } /* }}} */ /* {{{ mysqlnd_zend_mm_ecalloc */ static void * mysqlnd_zend_mm_ecalloc(unsigned int nmemb, size_t size MYSQLND_MEM_D) { return ecalloc(nmemb, size); } /* }}} */ /* {{{ mysqlnd_zend_mm_pecalloc */ static void * mysqlnd_zend_mm_pecalloc(unsigned int nmemb, size_t size, zend_bool persistent MYSQLND_MEM_D) { return pecalloc(nmemb, size, persistent); } /* }}} */ /* {{{ mysqlnd_zend_mm_erealloc */ static void * mysqlnd_zend_mm_erealloc(void *ptr, size_t new_size MYSQLND_MEM_D) { return erealloc(ptr, new_size); } /* }}} */ /* {{{ mysqlnd_zend_mm_perealloc */ static void * mysqlnd_zend_mm_perealloc(void *ptr, size_t new_size, zend_bool persistent MYSQLND_MEM_D) { return perealloc(ptr, new_size, persistent); } /* }}} */ /* {{{ mysqlnd_zend_mm_efree */ static void mysqlnd_zend_mm_efree(void * ptr MYSQLND_MEM_D) { efree(ptr); } /* }}} */ /* {{{ mysqlnd_zend_mm_pefree */ static void mysqlnd_zend_mm_pefree(void * ptr, zend_bool persistent MYSQLND_MEM_D) { pefree(ptr, persistent); } /* }}} */ /* {{{ mysqlnd_zend_mm_malloc */ static void * mysqlnd_zend_mm_malloc(size_t size MYSQLND_MEM_D) { return malloc(size); } /* }}} */ /* {{{ mysqlnd_zend_mm_calloc */ static void * mysqlnd_zend_mm_calloc(unsigned int nmemb, size_t size MYSQLND_MEM_D) { return calloc(nmemb, size); } /* }}} */ /* {{{ mysqlnd_zend_mm_realloc */ static void * mysqlnd_zend_mm_realloc(void * ptr, size_t new_size MYSQLND_MEM_D) { return realloc(ptr, new_size); } /* }}} */ /* {{{ mysqlnd_zend_mm_free */ static void mysqlnd_zend_mm_free(void * ptr MYSQLND_MEM_D) { free(ptr); } /* }}} */ /* {{{ mysqlnd_zend_mm_pestrndup */ static char * mysqlnd_zend_mm_pestrndup(const char * const ptr, size_t length, zend_bool persistent MYSQLND_MEM_D) { return pestrndup(ptr, length, persistent); } /* }}} */ /* {{{ mysqlnd_zend_mm_pestrdup */ static char * mysqlnd_zend_mm_pestrdup(const char * const ptr, zend_bool persistent MYSQLND_MEM_D) { return pestrdup(ptr, persistent); } /* }}} */ #endif PHPAPI struct st_mysqlnd_allocator_methods mysqlnd_allocator = { #if MYSQLND_DEBUG_MEMORY _mysqlnd_emalloc, _mysqlnd_pemalloc, _mysqlnd_ecalloc, _mysqlnd_pecalloc, _mysqlnd_erealloc, _mysqlnd_perealloc, _mysqlnd_efree, _mysqlnd_pefree, _mysqlnd_malloc, _mysqlnd_calloc, _mysqlnd_realloc, _mysqlnd_free, _mysqlnd_pestrndup, _mysqlnd_pestrdup #else mysqlnd_zend_mm_emalloc, mysqlnd_zend_mm_pemalloc, mysqlnd_zend_mm_ecalloc, mysqlnd_zend_mm_pecalloc, mysqlnd_zend_mm_erealloc, mysqlnd_zend_mm_perealloc, mysqlnd_zend_mm_efree, mysqlnd_zend_mm_pefree, mysqlnd_zend_mm_malloc, mysqlnd_zend_mm_calloc, mysqlnd_zend_mm_realloc, mysqlnd_zend_mm_free, mysqlnd_zend_mm_pestrndup, mysqlnd_zend_mm_pestrdup #endif }; /* Follows code borrowed from zend_builtin_functions.c because the functions there are static */ #if MYSQLND_UNICODE /* {{{ gettraceasstring() macros */ #define TRACE_APPEND_CHR(chr) \ *str = (char*)erealloc(*str, *len + 1 + 1); \ (*str)[(*len)++] = chr #define TRACE_APPEND_STRL(val, vallen) \ { \ int l = vallen; \ *str = (char*)erealloc(*str, *len + l + 1); \ memcpy((*str) + *len, val, l); \ *len += l; \ } #define TRACE_APPEND_USTRL(val, vallen) \ { \ zval tmp, copy; \ int use_copy; \ ZVAL_UNICODEL(&tmp, val, vallen, 1); \ zend_make_printable_zval(&tmp, ©, &use_copy); \ TRACE_APPEND_STRL(Z_STRVAL(copy), Z_STRLEN(copy)); \ zval_dtor(©); \ zval_dtor(&tmp); \ } #define TRACE_APPEND_ZVAL(zv) \ if (Z_TYPE_P((zv)) == IS_UNICODE) { \ zval copy; \ int use_copy; \ zend_make_printable_zval((zv), ©, &use_copy); \ TRACE_APPEND_STRL(Z_STRVAL(copy), Z_STRLEN(copy)); \ zval_dtor(©); \ } else { \ TRACE_APPEND_STRL(Z_STRVAL_P((zv)), Z_STRLEN_P((zv))); \ } #define TRACE_APPEND_STR(val) \ TRACE_APPEND_STRL(val, sizeof(val)-1) #define TRACE_APPEND_KEY(key) \ if (zend_ascii_hash_find(ht, key, sizeof(key), (void**)&tmp) == SUCCESS) { \ if (Z_TYPE_PP(tmp) == IS_UNICODE) { \ zval copy; \ int use_copy; \ zend_make_printable_zval(*tmp, ©, &use_copy); \ TRACE_APPEND_STRL(Z_STRVAL(copy), Z_STRLEN(copy)); \ zval_dtor(©); \ } else { \ TRACE_APPEND_STRL(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp)); \ } \ } /* }}} */ /* {{{ mysqlnd_build_trace_args */ static int mysqlnd_build_trace_args(zval **arg TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) { char **str; int *len; str = va_arg(args, char**); len = va_arg(args, int*); /* the trivial way would be to do: * conver_to_string_ex(arg); * append it and kill the now tmp arg. * but that could cause some E_NOTICE and also damn long lines. */ switch (Z_TYPE_PP(arg)) { case IS_NULL: TRACE_APPEND_STR("NULL, "); break; case IS_STRING: { int l_added; TRACE_APPEND_CHR('\''); if (Z_STRLEN_PP(arg) > 15) { TRACE_APPEND_STRL(Z_STRVAL_PP(arg), 15); TRACE_APPEND_STR("...', "); l_added = 15 + 6 + 1; /* +1 because of while (--l_added) */ } else { l_added = Z_STRLEN_PP(arg); TRACE_APPEND_STRL(Z_STRVAL_PP(arg), l_added); TRACE_APPEND_STR("', "); l_added += 3 + 1; } while (--l_added) { if ((unsigned char)(*str)[*len - l_added] < 32) { (*str)[*len - l_added] = '?'; } } break; } case IS_UNICODE: { int l_added; /* * We do not want to apply current error mode here, since * zend_make_printable_zval() uses output encoding converter. * Temporarily set output encoding converter to escape offending * chars with \uXXXX notation. */ zend_set_converter_error_mode(ZEND_U_CONVERTER(UG(output_encoding_conv)), ZEND_FROM_UNICODE, ZEND_CONV_ERROR_ESCAPE_JAVA); TRACE_APPEND_CHR('\''); if (Z_USTRLEN_PP(arg) > 15) { TRACE_APPEND_USTRL(Z_USTRVAL_PP(arg), 15); TRACE_APPEND_STR("...', "); l_added = 15 + 6 + 1; /* +1 because of while (--l_added) */ } else { l_added = Z_USTRLEN_PP(arg); TRACE_APPEND_USTRL(Z_USTRVAL_PP(arg), l_added); TRACE_APPEND_STR("', "); l_added += 3 + 1; } /* * Reset output encoding converter error mode. */ zend_set_converter_error_mode(ZEND_U_CONVERTER(UG(output_encoding_conv)), ZEND_FROM_UNICODE, UG(from_error_mode)); while (--l_added) { if ((unsigned char)(*str)[*len - l_added] < 32) { (*str)[*len - l_added] = '?'; } } break; } case IS_BOOL: if (Z_LVAL_PP(arg)) { TRACE_APPEND_STR("true, "); } else { TRACE_APPEND_STR("false, "); } break; case IS_RESOURCE: TRACE_APPEND_STR("Resource id #"); /* break; */ case IS_LONG: { long lval = Z_LVAL_PP(arg); char s_tmp[MAX_LENGTH_OF_LONG + 1]; int l_tmp = zend_sprintf(s_tmp, "%ld", lval); /* SAFE */ TRACE_APPEND_STRL(s_tmp, l_tmp); TRACE_APPEND_STR(", "); break; } case IS_DOUBLE: { double dval = Z_DVAL_PP(arg); char *s_tmp; int l_tmp; s_tmp = emalloc(MAX_LENGTH_OF_DOUBLE + EG(precision) + 1); l_tmp = zend_sprintf(s_tmp, "%.*G", (int) EG(precision), dval); /* SAFE */ TRACE_APPEND_STRL(s_tmp, l_tmp); /* %G already handles removing trailing zeros from the fractional part, yay */ efree(s_tmp); TRACE_APPEND_STR(", "); break; } case IS_ARRAY: TRACE_APPEND_STR("Array, "); break; case IS_OBJECT: { zval tmp; zstr class_name; zend_uint class_name_len; int dup; TRACE_APPEND_STR("Object("); dup = zend_get_object_classname(*arg, &class_name, &class_name_len TSRMLS_CC); ZVAL_UNICODEL(&tmp, class_name.u, class_name_len, 1); convert_to_string_with_converter(&tmp, ZEND_U_CONVERTER(UG(output_encoding_conv))); TRACE_APPEND_STRL(Z_STRVAL(tmp), Z_STRLEN(tmp)); zval_dtor(&tmp); if(!dup) { efree(class_name.v); } TRACE_APPEND_STR("), "); break; } default: break; } return ZEND_HASH_APPLY_KEEP; } /* }}} */ static int mysqlnd_build_trace_string(zval **frame TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) /* {{{ */ { char *s_tmp, **str; int *len, *num; long line; HashTable *ht = Z_ARRVAL_PP(frame); zval **file, **tmp; uint * level; level = va_arg(args, uint *); str = va_arg(args, char**); len = va_arg(args, int*); num = va_arg(args, int*); if (!*level) { return ZEND_HASH_APPLY_KEEP; } --*level; s_tmp = emalloc(1 + MAX_LENGTH_OF_LONG + 1 + 1); sprintf(s_tmp, "#%d ", (*num)++); TRACE_APPEND_STRL(s_tmp, strlen(s_tmp)); efree(s_tmp); if (zend_ascii_hash_find(ht, "file", sizeof("file"), (void**)&file) == SUCCESS) { if (zend_ascii_hash_find(ht, "line", sizeof("line"), (void**)&tmp) == SUCCESS) { line = Z_LVAL_PP(tmp); } else { line = 0; } TRACE_APPEND_ZVAL(*file); s_tmp = emalloc(MAX_LENGTH_OF_LONG + 2 + 1); sprintf(s_tmp, "(%ld): ", line); TRACE_APPEND_STRL(s_tmp, strlen(s_tmp)); efree(s_tmp); } else { TRACE_APPEND_STR("[internal function]: "); } TRACE_APPEND_KEY("class"); TRACE_APPEND_KEY("type"); TRACE_APPEND_KEY("function"); TRACE_APPEND_CHR('('); if (zend_ascii_hash_find(ht, "args", sizeof("args"), (void**)&tmp) == SUCCESS) { int last_len = *len; zend_hash_apply_with_arguments(Z_ARRVAL_PP(tmp) TSRMLS_CC, (apply_func_args_t)mysqlnd_build_trace_args, 2, str, len); if (last_len != *len) { *len -= 2; /* remove last ', ' */ } } TRACE_APPEND_STR(")\n"); return ZEND_HASH_APPLY_KEEP; } /* }}} */ #else /* PHP 5*/ /* {{{ gettraceasstring() macros */ #define TRACE_APPEND_CHR(chr) \ *str = (char*)erealloc(*str, *len + 1 + 1); \ (*str)[(*len)++] = chr #define TRACE_APPEND_STRL(val, vallen) \ { \ int l = vallen; \ *str = (char*)erealloc(*str, *len + l + 1); \ memcpy((*str) + *len, val, l); \ *len += l; \ } #define TRACE_APPEND_STR(val) \ TRACE_APPEND_STRL(val, sizeof(val)-1) #define TRACE_APPEND_KEY(key) \ if (zend_hash_find(ht, key, sizeof(key), (void**)&tmp) == SUCCESS) { \ TRACE_APPEND_STRL(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp)); \ } /* }}} */ static int mysqlnd_build_trace_args(zval **arg TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) /* {{{ */ { char **str; int *len; str = va_arg(args, char**); len = va_arg(args, int*); /* the trivial way would be to do: * conver_to_string_ex(arg); * append it and kill the now tmp arg. * but that could cause some E_NOTICE and also damn long lines. */ switch (Z_TYPE_PP(arg)) { case IS_NULL: TRACE_APPEND_STR("NULL, "); break; case IS_STRING: { int l_added; TRACE_APPEND_CHR('\''); if (Z_STRLEN_PP(arg) > 15) { TRACE_APPEND_STRL(Z_STRVAL_PP(arg), 15); TRACE_APPEND_STR("...', "); l_added = 15 + 6 + 1; /* +1 because of while (--l_added) */ } else { l_added = Z_STRLEN_PP(arg); TRACE_APPEND_STRL(Z_STRVAL_PP(arg), l_added); TRACE_APPEND_STR("', "); l_added += 3 + 1; } while (--l_added) { if ((*str)[*len - l_added] < 32) { (*str)[*len - l_added] = '?'; } } break; } case IS_BOOL: if (Z_LVAL_PP(arg)) { TRACE_APPEND_STR("true, "); } else { TRACE_APPEND_STR("false, "); } break; case IS_RESOURCE: TRACE_APPEND_STR("Resource id #"); /* break; */ case IS_LONG: { long lval = Z_LVAL_PP(arg); char s_tmp[MAX_LENGTH_OF_LONG + 1]; int l_tmp = zend_sprintf(s_tmp, "%ld", lval); /* SAFE */ TRACE_APPEND_STRL(s_tmp, l_tmp); TRACE_APPEND_STR(", "); break; } case IS_DOUBLE: { double dval = Z_DVAL_PP(arg); char *s_tmp; int l_tmp; s_tmp = emalloc(MAX_LENGTH_OF_DOUBLE + EG(precision) + 1); l_tmp = zend_sprintf(s_tmp, "%.*G", (int) EG(precision), dval); /* SAFE */ TRACE_APPEND_STRL(s_tmp, l_tmp); /* %G already handles removing trailing zeros from the fractional part, yay */ efree(s_tmp); TRACE_APPEND_STR(", "); break; } case IS_ARRAY: TRACE_APPEND_STR("Array, "); break; case IS_OBJECT: { char *class_name; zend_uint class_name_len; int dupl; TRACE_APPEND_STR("Object("); dupl = zend_get_object_classname(*arg, &class_name, &class_name_len TSRMLS_CC); TRACE_APPEND_STRL(class_name, class_name_len); if (!dupl) { efree(class_name); } TRACE_APPEND_STR("), "); break; } default: break; } return ZEND_HASH_APPLY_KEEP; } /* }}} */ static int mysqlnd_build_trace_string(zval **frame TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) /* {{{ */ { char *s_tmp, **str; int *len, *num; long line; HashTable *ht = Z_ARRVAL_PP(frame); zval **file, **tmp; uint * level; level = va_arg(args, uint *); str = va_arg(args, char**); len = va_arg(args, int*); num = va_arg(args, int*); if (!*level) { return ZEND_HASH_APPLY_KEEP; } --*level; s_tmp = emalloc(1 + MAX_LENGTH_OF_LONG + 1 + 1); sprintf(s_tmp, "#%d ", (*num)++); TRACE_APPEND_STRL(s_tmp, strlen(s_tmp)); efree(s_tmp); if (zend_hash_find(ht, "file", sizeof("file"), (void**)&file) == SUCCESS) { if (zend_hash_find(ht, "line", sizeof("line"), (void**)&tmp) == SUCCESS) { line = Z_LVAL_PP(tmp); } else { line = 0; } s_tmp = emalloc(Z_STRLEN_PP(file) + MAX_LENGTH_OF_LONG + 4 + 1); sprintf(s_tmp, "%s(%ld): ", Z_STRVAL_PP(file), line); TRACE_APPEND_STRL(s_tmp, strlen(s_tmp)); efree(s_tmp); } else { TRACE_APPEND_STR("[internal function]: "); } TRACE_APPEND_KEY("class"); TRACE_APPEND_KEY("type"); TRACE_APPEND_KEY("function"); TRACE_APPEND_CHR('('); if (zend_hash_find(ht, "args", sizeof("args"), (void**)&tmp) == SUCCESS) { int last_len = *len; zend_hash_apply_with_arguments(Z_ARRVAL_PP(tmp) TSRMLS_CC, (apply_func_args_t)mysqlnd_build_trace_args, 2, str, len); if (last_len != *len) { *len -= 2; /* remove last ', ' */ } } TRACE_APPEND_STR(")\n"); return ZEND_HASH_APPLY_KEEP; } /* }}} */ #endif PHPAPI char * mysqlnd_get_backtrace(uint max_levels, size_t * length TSRMLS_DC) { zval *trace; char *res = estrdup(""), **str = &res, *s_tmp; int res_len = 0, *len = &res_len, num = 0; if (max_levels == 0) { max_levels = 99999; } MAKE_STD_ZVAL(trace); zend_fetch_debug_backtrace(trace, 0, 0 TSRMLS_CC); 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); zval_ptr_dtor(&trace); if (max_levels) { s_tmp = emalloc(1 + MAX_LENGTH_OF_LONG + 7 + 1); sprintf(s_tmp, "#%d {main}", num); TRACE_APPEND_STRL(s_tmp, strlen(s_tmp)); efree(s_tmp); } res[res_len] = '\0'; *length = res_len; return res; } static struct st_mysqlnd_plugin_trace_log mysqlnd_plugin_trace_log_plugin = { { MYSQLND_PLUGIN_API_VERSION, "debug_trace", MYSQLND_VERSION_ID, MYSQLND_VERSION, "PHP License 3.01", "Andrey Hristov , Ulf Wendel , Georg Richter ", { NULL, /* no statistics , will be filled later if there are some */ NULL, /* no statistics */ }, { NULL /* plugin shutdown */ } }, {/* methods */ mysqlnd_debug_init, mysqlnd_get_backtrace } }; /* {{{ mysqlnd_debug_trace_plugin_register */ void mysqlnd_debug_trace_plugin_register(TSRMLS_D) { mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_plugin_trace_log_plugin TSRMLS_CC); } /* }}} */ /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: noet sw=4 ts=4 fdm=marker * vim<600: noet sw=4 ts=4 */