順風車運營研發團隊 黃桃php
線上零星會出現fpm進程coredump 及 fpm進程佔用內存超過限制等報警告,而且二者報警的時間上每每都比較接近,以下:mysql
時間上接近,那麼出現二者報警的緣由有多是相同的,登陸機器gdb調試coredump生成core文件:sql
cd /***/coresave/ gdb /***/php7/sbin/php-fpm -c core.php-fpm.12121.1528322653 bt #0 0x0000003f0f089770 in memcpy () from /lib64/libc.so.6 #1 0x00000000006403c5 in zend_string_init (stmt=0x7f505b478380, colno=<value optimized out>) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend_string.h:159 #2 pdo_mysql_stmt_describe (stmt=0x7f505b478380, colno=<value optimized out>) at /**/**/offcial_code/php/7.0.6/php-7.0.6/ext/pdo_mysql/mysql_statement.c:705 #3 0x000000000063a795 in pdo_stmt_describe_columns (stmt=0x7f505b478380) at /**/**/offcial_code/php/7.0.6/php-7.0.6/ext/pdo/pdo_stmt.c:206 #4 0x000000000063add5 in zim_PDOStatement_execute (execute_data=<value optimized out>, return_value=0x7f505b4157e0) at /**/**/offcial_code/php/7.0.6/php-7.0.6/ext/pdo/pdo_stmt.c:523 #5 0x00007f5054056bf1 in hp_execute_internal (execute_data=0x7f505b415830, return_value=0x7f505b4157e0) at /**/**/xhprof/extension/xhprof.c:1775 #6 0x0000000000879702 in ZEND_DO_FCALL_SPEC_HANDLER (execute_data=0x7f505b415680) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend_vm_execute.h:844 #7 0x0000000000841a40 in execute_ex (ex=<value optimized out>) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend_vm_execute.h:417 #8 0x00007f50540583d1 in hp_execute_ex (execute_data=0x7f505b415680) at /**/**/xhprof/extension/xhprof.c:1748 #9 0x000000000087957a in ZEND_DO_FCALL_SPEC_HANDLER (execute_data=0x7f505b4155a0) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend_vm_execute.h:800 #10 0x0000000000841a40 in execute_ex (ex=<value optimized out>) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend_vm_execute.h:417 #11 0x00007f50540583d1 in hp_execute_ex (execute_data=0x7f505b4155a0) at /**/**/xhprof/extension/xhprof.c:1748 #12 0x000000000087957a in ZEND_DO_FCALL_SPEC_HANDLER (execute_data=0x7f505b4154a0) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend_vm_execute.h:800 #13 0x0000000000841a40 in execute_ex (ex=<value optimized out>) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend_vm_execute.h:417 #14 0x00007f50540583d1 in hp_execute_ex (execute_data=0x7f505b4154a0) at /**/**/xhprof/extension/xhprof.c:1748 #15 0x000000000087957a in ZEND_DO_FCALL_SPEC_HANDLER (execute_data=0x7f505b4153b0) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend_vm_execute.h:800 #16 0x0000000000841a40 in execute_ex (ex=<value optimized out>) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend_vm_execute.h:417 #17 0x00007f50540583d1 in hp_execute_ex (execute_data=0x7f505b4153b0) at /**/**/xhprof/extension/xhprof.c:1748 #18 0x00000000007f52a8 in zend_call_function (fci=0x7ffebd295f40, fci_cache=0x7ffebd295f90) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend_execute_API.c:866 #19 0x0000000000709fef in zif_call_user_func_array (execute_data=0x7f505b415330, return_value=0x7f505b415300) at /**/**/offcial_code/php/7.0.6/php-7.0.6/ext/standard/basic_functions.c:4811 #20 0x00007f5054056bf1 in hp_execute_internal (execute_data=0x7f505b415330, return_value=0x7f505b415300) at /**/**/xhprof/extension/xhprof.c:1775 #21 0x0000000000879702 in ZEND_DO_FCALL_SPEC_HANDLER (execute_data=0x7f505b415290) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend_vm_execute.h:844 #22 0x0000000000841a40 in execute_ex (ex=<value optimized out>) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend_vm_execute.h:417 ---Type <return> to continue, or q <return> to quit--- #23 0x00007f50540583d1 in hp_execute_ex (execute_data=0x7f505b415290) at /**/**/xhprof/extension/xhprof.c:1748 #24 0x000000000087957a in ZEND_DO_FCALL_SPEC_HANDLER (execute_data=0x7f505b4150f0) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend_vm_execute.h:800 #25 0x0000000000841a40 in execute_ex (ex=<value optimized out>) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend_vm_execute.h:417 #26 0x00007f50540583d1 in hp_execute_ex (execute_data=0x7f505b4150f0) at /**/**/xhprof/extension/xhprof.c:1748 #27 0x0000000000887037 in ZEND_INCLUDE_OR_EVAL_SPEC_TMPVAR_HANDLER (execute_data=0x7f505b415030) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend_vm_execute.h:40848 #28 0x0000000000841a40 in execute_ex (ex=<value optimized out>) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend_vm_execute.h:417 #29 0x00007f50540583d1 in hp_execute_ex (execute_data=0x7f505b415030) at /**/**/xhprof/extension/xhprof.c:1748 #30 0x000000000089496b in zend_execute (op_array=0x7f505b46a000, return_value=<value optimized out>) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend_vm_execute.h:458 #31 0x0000000000802233 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /**/**/offcial_code/php/7.0.6/php-7.0.6/Zend/zend.c:1427 #32 0x00000000007a4b40 in php_execute_script (primary_file=0x7ffebd298990) at /**/**/offcial_code/php/7.0.6/php-7.0.6/main/main.c:2494 #33 0x00000000008a27fe in main (argc=<value optimized out>, argv=<value optimized out>) at /**/**/offcial_code/php/7.0.6/php-7.0.6/sapi/fpm/fpm/fpm_main.c:1968
能夠看到是在調用pdo擴展讀取MySQL數據後,而後copy讀取到的數據(memcpy)時出現的coredump數據庫
經過php的gdb工具.gdbint,來查看對應的zbacktracesegmentfault
source /tmp/php-src-PHP-7.0.6/.gdbinit zbacktrace [0x7f505b415830] PDOStatement->execute(array(1)[0x7f505b415890]) [internal function] [0x7f505b415680] ***\***\Mysql->run("SELECT\40*\40FROM\40table_name\40WHERE\40\40user_id\40=\40?", array(1)[0x7f505b4156f0], "select") /**/**/**/**/helper/mysql.php:270 [0x7f505b4155a0] ***\***\Mysql->select("table_name", "\40user_id\40=\40?\40", array(1)[0x7f505b415620]) /**/**/**/**/helper/mysql.php:364 [0x7f505b4154a0] ***\***\Model\User\FaceAuth->getInfoByUidForApi("144796772") /**/**/**/**/model/user/faceauth.php:200 [0x7f505b4153b0] ***\***\Controller\User\FaceAuthInfo->index(array(2)[0x7f505b415410]) /**/**/***/***/controller/user/faceauthinfo.php:50 [0x7f505b415330] call_user_func_array(array(2)[0x7f505b415390], array(1)[0x7f505b4153a0]) [internal function] [0x7f505b415290] ***\Framework\Base\Router->run(array(2)[0x7f505b4152f0]) /**/**/**/**/base/router.php:96 [0x7f505b4150f0] (main) /**/**/**/**/framework.php:139 [0x7f505b415030] (main) /**/**/***/***/index.php:46
是在執行「SELECT40*40FROM40table_name40WHERE4040user_id40=40? 」出現的問題api
此時兩種可能,第一個傳入的參數是否有問題?第二個是此語句返回的數據是否有問題?數組
先驗證傳入參數:php7
繼續看當時傳入的參數是什麼?打印地址 0x7f505b415620 當時的值數據結構
(gdb) print ((zval *)0x7f505b415620) $1 = (zval *) 0x7f505b415620 (gdb) p $1 $2 = (zval *) 0x7f505b415620 (gdb) p *$1 $3 = {value = {lval = 139983105408624, dval = 6.9160843380575109e-310, counted = 0x7f505b45be70, str = 0x7f505b45be70, arr = 0x7f505b45be70, obj = 0x7f505b45be70, res = 0x7f505b45be70, ref = 0x7f505b45be70, ast = 0x7f505b45be70, zv = 0x7f505b45be70, ptr = 0x7f505b45be70, ce = 0x7f505b45be70, func = 0x7f505b45be70, ww = {w1 = 1531297392, w2 = 32592}}, u1 = {v = {type = 7 '\a', type_flags = 28 '\034', const_flags = 0 '\000', reserved = 0 '\000'}, type_info = 7175}, u2 = {var_flags = 0, next = 0, cache_slot = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0}} //此時的參數傳入的是個數組 type=7;繼續查看數組內容是啥;, (gdb) p *$1.value.arr.arData.key Cannot access memory at address 0x0 //key爲空 p *$1.value.arr.arData.val.value.str.val@30 $10 = "144796772\000\000\000\377\377\377\377\001\000\000\000\006\000\000\000\bH\024\252+0" //傳入參數user_id=144796772,入參沒問題
那當時語句返回的數據呢?函數
根據前面查看core文件的信息能夠知道,返回數據後,最終調用了pdo_mysql_stmt_describe 函數,也正是在這以後出現的coredump,此函數中變量stmt 存儲着sql語句執行後的返回值。
繼續看返回stmt裏面值是什麼?//pdo_mysql_stmt_describe (stmt=0x7f505b478380, colno=<value optimized out>) (注:打印某個地址的值時,須要知道變量的類型,
對應的數據結構也需在源碼中看,不然沒法繼續跟進,此處查看stmt 變量的類型爲pdo_stmt_t )
先記住幾個變量:
(gdb) print ((pdo_stmt_t *)0x7f505b478380) $24 = (pdo_stmt_t *) 0x7f505b478380 //stmt變量對應的是$24 (gdb) p (pdo_mysql_stmt *)$24.driver_data $31 = (pdo_mysql_stmt *) 0x7f505b45a500 (gdb) p (struct pdo_column_data *) $24.columns $41 = (struct pdo_column_data *) 0x7f505b4b5300 pdo_mysql_stmt_describe函數的php的源碼以下 : static int pdo_mysql_stmt_describe(pdo_stmt_t *stmt, int colno) /* {{{ */ { pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;//變量$31 struct pdo_column_data *cols = stmt->columns;//$變量$41 int i; PDO_DBG_ENTER("pdo_mysql_stmt_describe"); PDO_DBG_INF_FMT("stmt=%p", S->stmt); if (!S->result) { PDO_DBG_RETURN(0); } if (colno >= stmt->column_count) { /* error invalid column */ PDO_DBG_RETURN(0); } /* fetch all on demand, this seems easiest ** if we've been here before bail out */ if (cols[0].name) { PDO_DBG_RETURN(1); } for (i = 0; i < stmt->column_count; i++) { //stmt->column_count的值是17,對應的是table_name表中列的總數 if (S->H->fetch_table_names) { cols[i].name = strpprintf(0, "%s.%s", S->fields[i].table, S->fields[i].name); } else { cols[i].name = zend_string_init(S->fields[i].name, S->fields[i].name_length, 0); //猜測:一、coredump是在調用zend_string_init函數裏面的memcpy (),猜測越界訪問受保護的內存? //二、內存佔用超出PHP限制大小是否也是在這出的問題呢,調用zend_string_init時傳入的S->fields[i].name_length 過大致使? cols[i].precision = S->fields[i].decimals; cols[i].maxlen = S->fields[i].length; #ifdef PDO_USE_MYSQLND if (S->stmt) { cols[i].param_type = PDO_PARAM_ZVAL; } else #endif { cols[i].param_type = PDO_PARAM_STR; } } PDO_DBG_RETURN(1); }
猜測:一、coredump是在調用zend_string_init函數裏面的memcpy (),猜測越界訪問受保護的內存?二、內存佔用超出PHP限制大小是否也是在這出的問題呢,調用zend_string_init時傳入的S->fields[i].name_length 過大致使?繼續 打印驗證;
打印當時返回的數據來驗證前面的猜測:
//先看一下S->fields[i] 變量的值 (gdb) p $31.fields[0] $79 = {sname = 0x7f505b45fdc0, name = 0x7f505b45fdd8 "id", org_name = 0x7f505b403a17 "id", table = 0x7f505b403a03 "table_name", org_table = 0x7f505b403a0d "table_name", db = 0x7f505b4039f4 "db_name", catalog = 0x7f505b4039f0 "def", def = 0x0, length = 20, max_length = 0, name_length = 2, org_name_length = 2, table_length = 9, org_table_length = 9, db_length = 14, catalog_length = 3, def_length = 0, flags = 49699, decimals = 0, charsetnr = 63, type = MYSQL_TYPE_LONGLONG, root = 0x7f505b4039f0 "def", root_len = 45} //name=「id」,table="table_name",很明顯是數據表中某一列的信息信息數據,pdo_mysql_stmt_describe函數的做用是變量S->fields 並賦值給變量cols; //繼續打印: (gdb) p $31.fields[0].name_length $64 = 2 (gdb) p $31.fields[1].name_length $65 = 7 (gdb) p $31.fields[2].name_length $66 = 10 (gdb) p $31.fields[3].name_length $67 = 13 (gdb) p $31.fields[4].name_length $68 = 16 (gdb) p $31.fields[13].name_length $69 = 5 (gdb) p $31.fields[14].name_length $70 = 8 (gdb) p $31.fields[15].name_length $71 = 0 (gdb) p $31.fields[16].name_length $72 = 5863687//此時罪魁禍首出現了,讀取的p $31.fields[16].name_length 出現了一個巨大的值; (gdb) p $31.fields[16]//打印變量看,出現了訪問越界 $109 = {sname = 0x7f505b464028, name = 0xffffffff00001406 <Address 0xffffffff00001406 out of bounds>,//訪問越界 org_name = 0x800000000b88b287 <Address 0x800000000b88b287 out of bounds>, table = 0x7f505b45d0c0 "Asia/Shanghai", org_table = 0x7f505b45d120 "\020\321E[P\177", db = 0xffffffff00001406 <Address 0xffffffff00001406 out of bounds>, catalog = 0x80000652f67b748b <Address 0x80000652f67b748b out of bounds>, def = 0x7f505b45d140 "0j\003IP\177", length = 139983105413536, max_length = 8589939718, name_length = 5863687, org_name_length = 2147483648, table_length = 1531302336, org_table_length = 32592, db_length = 1531302432, catalog_length = 32592, def_length = 5126, flags = 3, decimals = 1929407563, charsetnr = 2147537079, type = 1531302464, root = 0x7f505b464078 "\300MF[P\177", root_len = 18446744069414589446}
stmt->driver_data->fields數組實際的大小與 stmt->column_count標記大小不一致,致使了在循環該數組時,出現內存訪問越界,若是此時訪問的內存受系統保護則coredump,不然也取到一個很大的值,初始化一個極大的字符串,致使內存佔用超出PHP限制的大小;
爲何stmt->driver_data->fields數組與 stmt->column_count標記的大小不一致呢?往前追溯stmt變量的初始化,看是否存在問題?
在PHP源碼中函數 pdo_mysql_stmt_execute->pdo_mysql_stmt_execute_prepared_mysqlnd,對column_count 及fields 有作定義,具體以下:
static int pdo_mysql_stmt_execute_prepared_mysqlnd(pdo_stmt_t *stmt) /* {{{ */ { pdo_mysql_stmt *S = stmt->driver_data; pdo_mysql_db_handle *H = S->H; int i; PDO_DBG_ENTER("pdo_mysql_stmt_execute_prepared_mysqlnd"); if (mysql_stmt_execute(S->stmt)) {//mysqlnd/mysqlnd_ps.c 619 pdo_mysql_error_stmt(stmt); PDO_DBG_RETURN(0); } if (S->result) { /* TODO: add a test to check if we really have zvals here... */ mysql_free_result(S->result); S->result = NULL; } /* for SHOW/DESCRIBE and others the column/field count is not available before execute */ stmt->column_count = mysql_stmt_field_count(S->stmt); for (i = 0; i < stmt->column_count; i++) { mysqlnd_stmt_bind_one_result(S->stmt, i); } S->result = mysqlnd_stmt_result_metadata(S->stmt); if (S->result) { S->fields = mysql_fetch_fields(S->result); /* if buffered, pre-fetch all the data */ if (H->buffered) { if (mysql_stmt_store_result(S->stmt)) { PDO_DBG_RETURN(0); } } } pdo_mysql_stmt_set_row_count(stmt); PDO_DBG_RETURN(1); } 定義代碼: stmt->column_count = mysql_stmt_field_count(S->stmt);//返回17 S->fields = S->fields = mysql_fetch_fields(S->result);//實際數組則只有15
問題的根源就是 宏mysql_stmt_field_count(獲取數組大小) 與 宏mysql_fetch_fields(獲取數組內容) 返回對應不上致使的;
這兩個宏定義以下:
mysql_stmt_field_count: #define mysql_stmt_field_count(s) mysqlnd_stmt_field_count((s)) #define mysqlnd_stmt_field_count(stmt) (stmt)->m->get_field_count((stmt))//函數指針 php_mysqlnd_stmt_field_count_pub 函數 mysql_fetch_fields: #define mysql_fetch_fields(r) mysqlnd_fetch_fields((r))//函數指針: #define mysqlnd_fetch_fields(result) (result)->m.fetch_fields((result)) //函數指針 php_mysqlnd_res_fetch_fields_pub 函數
看這兩個函數實現,函數實現:
stmt->column_count:php_mysqlnd_stmt_field_count_pub函數->最終取的是 stmt->driver_data.stmt,data.field_count;
stmt->driver_data->fields數組:php_mysqlnd_res_fetch_fields_pub函數->php_mysqlnd_res_meta_fetch_field_pub函數,最終取的是:stmt->driver_data.result.meta.fields 此數組的長度與 stmt->driver_data.stmt.data.field_count 取得值存在偏差;
繼續跟進爲何二者存在誤差?
猜想緣由:猜想緣由:stmt->driver_data.result.meta.fields 此數組的長度與 stmt->driver_data.stmt.data.field_count是兩次連接讀取的;
先看select函數的核心實現:
$t1 = microtime(true); $pdoStmt = $this->pdo->prepare($sql); //調用的是 php_mysqlnd_stmt_prepare_pub $prepareSuccess = true; $return = true; $result = $pdoStmt->execute($bind);//調用的是 php_mysqlnd_stmt_execute_pub gdb調試,依次打上斷點: b php_mysqlnd_stmt_prepare_pub b php_mysqlnd_stmt_execute_pub
在 php_mysqlnd_stmt_execute_pub 函數能夠看到 該變量( stmt->driver_data.stmt.data.field_count )已經被初始化(在php_mysqlnd_stmt_prepare_pub初始化),而stmt->driver_data.result.meta.fields 的初始化則是在以後的php_mysqlnd_res_read_result_metadata_pub函數中賦值,這就致使了二者之間可能不一致的狀況;
php_mysqlnd_res_read_result_metadata_pub函數具體bt信息以下:
#0 php_mysqlnd_res_read_result_metadata_pub (result=0x7ffff61e8040, conn=0x7ffff61b0400) at /**/offcial_code/php/7.0.6/php-7.0.6/ext/mysqlnd/mysqlnd_result.c:357 #1 0x0000000000798971 in mysqlnd_query_read_result_set_header (conn=0x7ffff61b0400, s=<value optimized out>) at /**/offcial_code/php/7.0.6/php-7.0.6/ext/mysqlnd/mysqlnd_result.c:533 #2 0x000000000079eb71 in mysqlnd_stmt_execute_parse_response (s=0x7ffff61f0000, type=<value optimized out>) at /**/offcial_code/php/7.0.6/php-7.0.6/ext/mysqlnd/mysqlnd_ps.c:504 #3 0x000000000079bc6c in php_mysqlnd_stmt_execute_pub (s=0x7ffff61f0000) at /**/offcial_code/php/7.0.6/php-7.0.6/ext/mysqlnd/mysqlnd_ps.c:614
驗證一下此處的stmt變量是否爲php_mysqlnd_stmt_prepare_pub函數中使用的變量,打印兩個變量地址對比結果以下:
php_mysqlnd_stmt_prepare_pub 時的 stmt 地址 : $38 = (MYSQLND_STMT_DATA *) 0x7fffed40a700
php_mysqlnd_stmt_execute_pub 時的 S->stmt.data 的stmt地址 : $46 = (MYSQLND_STMT_DATA *) 0x7fffed40a700
繼續跟進代碼,php_mysqlnd_stmt_execute_pub函數的總體的調用流程以下:
第一步: if(!stmt){}else{//stmt 爲第一次連接mysql,調用(php_mysqlnd_stmt_prepare_pub)執行返回的數據 result = stmt.result //result第一次函數調用返回的結果,裏面包含了表的列數信息等 mysqlnd_result.c:525行 } 第二步: //初始化result->meta,傳入列數爲result->field_count,即第一次調用mysql返回的列數,問題的關鍵點在這,此處應該取 rset_header->field_count 就不會出問題; result->meta = result->m.result_meta_init(result->field_count, result->persistent); //mysqlnd_result.c 371行 第三步: //rset_header變量的初始化爲mysqlnd_result.c:413行 conn->field_count = rset_header->field_count;//rset_header 爲第二次連接mysql讀取到的返回值,(此階段爲php_mysqlnd_stmt_execute_pub函數中) mysqlnd_result.c:499行 第四步: stmt->field_count = stmt->result->field_count = conn->field_count;//把第二次連接mysql讀取到的列數賦值給stmt->field_count //mysqlnd_ps.c:541行 第五步: stmt->column_count = mysql_stmt_field_count(S->stmt);//return stmt? stmt->field_count : 0;//把stmt->driver_data.stmt.field_count(即第四步的變量stmt->field_count) 賦值給 stmt->column_count //mysql_statement.c:293行 第六步: S->fields = mysql_fetch_fields(S->result);//把stmt->driver_data.result.meta.fields 賦值給 stmt->driver_data.fields //mysql_statement.c:300行 第七步: 調用 pdo_mysql_stmt_describe函數 執行以下代碼,若是兩次讀數據庫返回的field_count不一致,則可能形成coredump及內存超出限制大小 for (i = 0; i < stmt->column_count; i++) { cols[i].name = zend_string_init(S->fields[i].name, S->fields[i].name_length, 0); cols[i].precision = S->fields[i].decimals; │ cols[i].maxlen = S->fields[i].length; }
猜想的緣由爲:在更改表結構時,由於存在一主多從,表結構從15列改成了17列,第一次php_mysqlnd_stmt_prepare_pub預執行時讀取到的從庫 A的表信息,此時剛好表結構並未更改過來,列數爲15,並把15存儲在了 stmt.driver_data.stmt.data.result.field_count 變量中,第二次php_mysqlnd_stmt_execute_pub函數讀取到從庫B的表信息,次數表結構已更改成17列,取到的列數數爲17,可是數組內容的初始化用的爲第一次取到的列數15,即上文第二步執行的過程;以後遍歷循環數組內容時用的列數爲第二次返回的列數17,所以這時出現了內存越界訪問,也就是形成前文的報警;
建議
一、做者在遍歷循環數組內容(stmt->driver_data->fields)時 不該該取 stmt->column_count 值,而應該取stmt->driver_data.result.meta.field_count更合適,其 與 stmt->driver_data.result.meta.fields(數組內容) 在同一個結構體中,且同時被初始化,不被其餘邏輯污染;
二、做者在初始化列數組內容時(result->m.result_meta_init(result->field_count, result->persistent); //mysqlnd_result.c 371行 )不該該用第一次連接MySQL返回的列數,而應該用第二次連接MySQL取到的列數,即rset_header->field_count ;
gdb斷點處:
b php_mysqlnd_stmt_prepare_pub
b pdo_stmt.c:511
b mysqlnd_ps.c:540
b php_mysqlnd_stmt_execute_pub
b mysqlnd_stmt_execute_parse_response
b mysqlnd_result_meta_init
此次問題跟進頗爲不易,pdo全部的數據結構比較多且雜,代碼上命名又寫成一致,讓人有點眼花繚亂,緣由也只能一次又一次的否定從新跟進定位,你們可按照前面寫的,「php_mysqlnd_stmt_execute_pub函數的總體的調用流程」中每一個步驟列的代碼行,逐個打上斷點,gdb逐行跟進驗證;
php版本爲:PHP 7.0.6
粗略整理了這次跟進pdo核心所依賴的數據結構:pdo結構體梳理