無理して出力しようとするのは良くないと思うアレコレ
以前、「未定義の定数の扱い」というエントリで気に入らない挙動を何とかしたのですが、その続き。
どういった挙動が気に入らないかは、最初の実行結果を確認して頂くという事で。
まずは、玉(アーカイブ)を展開してディレクトリを移動。その後、バイナリをビルド。
% cd /usr/local/src % gzip -dc php-4.4.4.tar.gz | tar xf - % cd php-4.4.4 % ./configure --disable-all --enable-debug % make
ビルドが完了したら、処理を実行。
% sapi/cli/php -r ' \ $PTR = fopen( "configure", "r" ); \ echo null; echo "\n"; \ echo false; echo "\n"; \ echo true; echo "\n"; \ echo $PTR; echo "\n"; \ echo new stdClass; echo "\n"; \ echo array( ); echo "\n"; '; 結果 ---- 1 Resource id #4 Object Array
うーん。。。やっぱり、あり得ない!。。。配列をechoしたヤツの考えも。。。きぃ。
デバッグ用途に用いるなら(まだ)理解はできるのですが。本番サービスで、この挙動はあり得ないだろう。。。という訳で。
今回は、この挙動を変えてみる(何も出力しない)事に挑戦してみます。今回も内部の動作を確認しつつ進行します。
(多分、該当しないんだろうけど)まずはgdbを起動してブレーク・ポイントの設定。
% gdb sapi/cli/php (gdb) b print Function "print" not defined. Make breakpoint pending on future shared library load? (y or [n]) (gdb) b echo Function "echo" not defined. Make breakpoint pending on future shared library load? (y or [n]) (gdb) q
(予想通り)ブレーク・ポイントが見つからないので、バイナリに対して「print」キーワードで検索。
% strings sapi/cli/php | egrep print vfprintf php_info_print_box_start zif_print_r print_class vspprintf zend_printf php_sprintf zend_print_zval_r ap_php_snprintf zif_vprintf php_info_print_style zend_print_zval php_print_credits zend_print_zval_ex php_info_print_css php_print_info php_info_print_table_start _php_stream_printf zif_user_sprintf ap_php_vsnprintf php_info_print_table_row zend_print_zval_r_ex zend_make_printable_zval php_info_print_table_end php_printf php_info_print_box_end php_info_print_table_header zend_sprintf zif_quoted_printable_decode zend_do_print zif_user_printf zend_locale_sprintf_double zif_vsprintf php_info_print_hr php_info_print_table_colspan_header php_print_info_htmlhead zend_print_variable print sprintf printf vprintf vsprintf quoted_printable_decode print_r /usr/local/src/php-4.4.4/ext/standard/formatted_print.c /usr/local/src/php-4.4.4/ext/standard/quot_print.c Illegal length modifier specified '%c' in s[np]printf call Illegal length modifier specified '%c' in s[np]printf call /usr/local/src/php-4.4.4/main/spprintf.c
かなりの量で見る気が失せる。。。キーワードを「echo」にして再挑戦。
% strings sapi/cli/php | egrep echo zend_do_echo echo_expr_list
非常にスッキリ。こっちで進行する事にします。
gdbを起動して検索結果から得られた情報の中から、それっぽいヤツ(zend_do_echo)をブレーク・ポイントに設定。
% gdb sapi/cli/php (gdb) b zend_do_echo Breakpoint 1 at 0x80f7322: file /usr/local/src/php-4.4.4/Zend/zend_compile.c, line 317. (gdb) r -r 'echo "Hello World.\n";' Starting program: /usr/local/src/php-4.4.4/sapi/cli/php -r 'echo "Hello World.\n";' Breakpoint 1, zend_do_echo (arg=0xbfffe890) at /usr/local/src/php-4.4.4/Zend/zend_compile.c:317 317 zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); (gdb) bt #0 zend_do_echo (arg=0xbfffe890) at /usr/local/src/php-4.4.4/Zend/zend_compile.c:317 #1 0x080ebe63 in zendparse () at Zend/zend_language_parser.c:3350 #2 0x080ef15a in compile_string (source_string=0xbffff790, filename=0x814b254 "Command line code") at Zend/zend_language_scanner.c:3187 #3 0x080fea26 in zend_eval_string (str=0xbffffbc7 "echo \"Hello World.\\n\";", retval_ptr=0x0, string_name=0x814b254 "Command line code") at /usr/local/src/php-4.4.4/Zend/zend_execute_API.c:635 #4 0x08122e12 in main (argc=3, argv=0xbffffa74) at /usr/local/src/php-4.4.4/sapi/cli/php_cli.c:871 (gdb) r -r '$A = "Hello World.\n"; echo $A;' The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /usr/local/src/php-4.4.4/sapi/cli/php -r '$A = "Hello World.\n"; echo $A;' Breakpoint 1, zend_do_echo (arg=0xbfffe880) at /usr/local/src/php-4.4.4/Zend/zend_compile.c:317 317 zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); (gdb) bt #0 zend_do_echo (arg=0xbfffe880) at /usr/local/src/php-4.4.4/Zend/zend_compile.c:317 #1 0x080ebe63 in zendparse () at Zend/zend_language_parser.c:3350 #2 0x080ef15a in compile_string (source_string=0xbffff780, filename=0x814b254 "Command line code") at Zend/zend_language_scanner.c:3187 #3 0x080fea26 in zend_eval_string (str=0xbffffbbe "$A = \"Hello World.\\n\"; echo $A;", retval_ptr=0x0, string_name=0x814b254 "Command line code") at /usr/local/src/php-4.4.4/Zend/zend_execute_API.c:635 #4 0x08122e12 in main (argc=3, argv=0xbffffa64) at /usr/local/src/php-4.4.4/sapi/cli/php_cli.c:871 (gdb) q The program is running. Exit anyway? (y or n) y
文字リテラルそのものを出力する方法と、(文字リテラルを変数に代入してから)変数を出力する方法を実行してみた。
バックトレースの内容を見る限りでは、どちらも差が無い事が分かります。やっぱりバックトレースは非常に便利。
「zend_eval_string」関数や「compile_string」関数でコンパイル作業でもやってるのかな。。。と思いつつ、ここは本題ではないので(また別の機会にでも)。
バックトレースの情報から関数の実体は「Zend/zend_compile.c」の中にある事が分かったので、中身を覗いてみる。
% less -N Zend/zend_compile.c 〜 省略 〜 302 void zend_do_print(znode *result, znode *arg TSRMLS_DC) 303 { 304 zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); 305 306 opline->result.op_type = IS_TMP_VAR; 307 opline->result.u.var = get_temporary_variable(CG(active_op_array)); 308 opline->opcode = ZEND_PRINT; 309 opline->op1 = *arg; 310 SET_UNUSED(opline->op2); 311 *result = opline->result; 312 } 313 314 315 void zend_do_echo(znode *arg TSRMLS_DC) 316 { 317 zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); 318 319 opline->opcode = ZEND_ECHO; 320 opline->op1 = *arg; 321 SET_UNUSED(opline->op2); 322 }
非常にショボイ。。。直ぐ上に「zend_do_print」関数があるね。
これだけでは情報が少ないので「ZEND_ECHO」のキーワードでディレクトリ内を検索。
% find -type f | xargs egrep -l ZEND_ECHO ./Zend/zend_execute.c ./Zend/zend_compile.c ./Zend/zend_compile.h
手始めに「Zend/zend_execute.c」の中を覗いてみる。
% less -N Zend/zend_execute.c 〜 省略 〜 1040 ZEND_API void execute(zend_op_array *op_array TSRMLS_DC) 1041 { 〜 省略 〜 1097 zend_clean_garbage(TSRMLS_C); 1098 1099 switch(EX(opline)->opcode) { 1100 case ZEND_ADD: 1101 EG(binary_op) = add_function; 1102 goto binary_op_addr; 1103 case ZEND_SUB: 1104 EG(binary_op) = sub_function; 1105 goto binary_op_addr; 1106 case ZEND_MUL: 1107 EG(binary_op) = mul_function; 1108 goto binary_op_addr; 1109 case ZEND_DIV: 1110 EG(binary_op) = div_function; 1111 goto binary_op_addr; 1112 case ZEND_MOD: 1113 EG(binary_op) = mod_function; 1114 goto binary_op_addr; 〜 省略 〜 1263 case ZEND_PRINT: 1264 zend_print_variable(get_zval_ptr(&EX(opline)->op1, EX(Ts), &EG(free_op1), BP_VAR_R)); 1265 EX(Ts)[EX(opline)->result.u.var].tmp_var.value.lval = 1; 1266 EX(Ts)[EX(opline)->result.u.var].tmp_var.type = IS_LONG; 1267 FREE_OP(EX(Ts), &EX(opline)->op1, EG(free_op1)); 1268 NEXT_OPCODE(); 1269 case ZEND_ECHO: 1270 zend_print_variable(get_zval_ptr(&EX(opline)->op1, EX(Ts), &EG(free_op1), BP_VAR_R)); 1271 FREE_OP(EX(Ts), &EX(opline)->op1, EG(free_op1)); 1272 NEXT_OPCODE(); 1273 case ZEND_FETCH_R: 1274 zend_fetch_var_address(EX(opline), EX(Ts), BP_VAR_R TSRMLS_CC); 1275 AI_USE_PTR(EX(Ts)[EX(opline)->result.u.var].var); 1276 NEXT_OPCODE(); 1277 case ZEND_FETCH_W: 1278 zend_fetch_var_address(EX(opline), EX(Ts), BP_VAR_W TSRMLS_CC); 1279 NEXT_OPCODE();
おっ!いきなり大当たりっぽい。(w
1263行目の「ZEND_PRINT」にしても1269行目の「ZEND_ECHO」にしても「zend_print_variable」関数を呼んでいる事が分かります。名前から察するに、間違いないだろう。。。きっと。
という訳で「zend_print_variable」関数をブレーク・ポイントに設定。
% gdb sapi/cli/php (gdb) b zend_print_variable Breakpoint 1 at 0x810692b: file /usr/local/src/php-4.4.4/Zend/zend_variables.c, line 151. (gdb) r -r 'echo "Hello World.\n";' Starting program: /usr/local/src/php-4.4.4/sapi/cli/php -r 'echo "Hello World.\n";' Breakpoint 1, zend_print_variable (var=0x8181bdc) at /usr/local/src/php-4.4.4/Zend/zend_variables.c:151 151 return zend_print_zval(var, 0); (gdb) bt #0 zend_print_variable (var=0x8181bdc) at /usr/local/src/php-4.4.4/Zend/zend_variables.c:151 #1 0x0811a0f2 in execute (op_array=0x81819e4) at /usr/local/src/php-4.4.4/Zend/zend_execute.c:1270 #2 0x080fea77 in zend_eval_string (str=0xbffffbc7 "echo \"Hello World.\\n\";", retval_ptr=0x0, string_name=0x814b254 "Command line code") at /usr/local/src/php-4.4.4/Zend/zend_execute_API.c:647 #3 0x08122e12 in main (argc=3, argv=0xbffffa74) at /usr/local/src/php-4.4.4/sapi/cli/php_cli.c:871 (gdb) q The program is running. Exit anyway? (y or n) y
一応、バックトレースの確認もしておく。
「zend_do_echo」関数をブレークポイントに設定した時は「zend_eval_string」関数の次は「compile_string」関数だったのですが、「zend_print_variable」関数をブレークポイントに設定した時は「zend_eval_string」関数の次は「execute」関数になってますね(その後に「zend_print_variable」関数が登場)。
バックトレースの情報から関数の実体は「Zend/zend_variables.c」の中にある事が分かったので、中身を覗いてみる。
% less -N Zend/zend_variables.c 〜 省略 〜 149 ZEND_API int zend_print_variable(zval *var) 150 { 151 return zend_print_zval(var, 0); 152 }
。。。こっちもショボい。_| ̄|○
めげずに「zend_print_zval」関数をブレーク・ポイントに設定。
% gdb sapi/cli/php (gdb) b zend_print_zval Breakpoint 1 at 0x8106e19: file /usr/local/src/php-4.4.4/Zend/zend.c, line 192. (gdb) r -r 'echo "Hello World.\n";' Starting program: /usr/local/src/php-4.4.4/sapi/cli/php -r 'echo "Hello World.\n";' Breakpoint 1, zend_print_zval (expr=0x8181bdc, indent=0) at /usr/local/src/php-4.4.4/Zend/zend.c:192 192 return zend_print_zval_ex(zend_write, expr, indent); (gdb) bt #0 zend_print_zval (expr=0x8181bdc, indent=0) at /usr/local/src/php-4.4.4/Zend/zend.c:192 #1 0x0810693e in zend_print_variable (var=0x8181bdc) at /usr/local/src/php-4.4.4/Zend/zend_variables.c:151 #2 0x0811a0f2 in execute (op_array=0x81819e4) at /usr/local/src/php-4.4.4/Zend/zend_execute.c:1270 #3 0x080fea77 in zend_eval_string (str=0xbffffbc7 "echo \"Hello World.\\n\";", retval_ptr=0x0, string_name=0x814b254 "Command line code") at /usr/local/src/php-4.4.4/Zend/zend_execute_API.c:647 #4 0x08122e12 in main (argc=3, argv=0xbffffa74) at /usr/local/src/php-4.4.4/sapi/cli/php_cli.c:871 (gdb) q The program is running. Exit anyway? (y or n) y
バックトレースの情報から関数の実体は「Zend/zend.c」の中にある事が分かったので、中身を覗いてみる。
% less -N Zend/zend.c 〜 省略 〜 49 ZEND_API zend_write_func_t zend_write; 〜 省略 〜 142 ZEND_API void zend_make_printable_zval(zval *expr, zval *expr_copy, int *use_copy) 143 { 144 if (expr->type==IS_STRING) { 145 *use_copy = 0; 146 return; 147 } 148 switch (expr->type) { 149 case IS_NULL: 150 expr_copy->value.str.len = 0; 151 expr_copy->value.str.val = empty_string; 152 break; 153 case IS_BOOL: 154 if (expr->value.lval) { 155 expr_copy->value.str.len = 1; 156 expr_copy->value.str.val = estrndup("1", 1); 157 } else { 158 expr_copy->value.str.len = 0; 159 expr_copy->value.str.val = empty_string; 160 } 161 break; 162 case IS_RESOURCE: 163 expr_copy->value.str.val = (char *) emalloc(sizeof("Resource id #")-1 + MAX_LENGTH_OF_LONG); 164 expr_copy->value.str.len = sprintf(expr_copy->value.str.val, "Resource id #%ld", expr->value.lval); 165 break; 166 case IS_ARRAY: 167 expr_copy->value.str.len = sizeof("Array")-1; 168 expr_copy->value.str.val = estrndup("Array", expr_copy->value.str.len); 169 break; 170 case IS_OBJECT: 171 expr_copy->value.str.len = sizeof("Object")-1; 172 expr_copy->value.str.val = estrndup("Object", expr_copy->value.str.len); 173 break; 174 case IS_DOUBLE: 175 *expr_copy = *expr; 176 zval_copy_ctor(expr_copy); 177 zend_locale_sprintf_double(expr_copy ZEND_FILE_LINE_CC); 178 break; 179 default: 180 *expr_copy = *expr; 181 zval_copy_ctor(expr_copy); 182 convert_to_string(expr_copy); 183 break; 184 } 185 expr_copy->type = IS_STRING; 186 *use_copy = 1; 187 } 188 189 190 ZEND_API int zend_print_zval(zval *expr, int indent) 191 { 192 return zend_print_zval_ex(zend_write, expr, indent); 193 } 194 195 196 ZEND_API int zend_print_zval_ex(zend_write_func_t write_func, zval *expr, int indent) 197 { 198 zval expr_copy; 199 int use_copy; 200 201 zend_make_printable_zval(expr, &expr_copy, &use_copy); 202 if (use_copy) { 203 expr = &expr_copy; 204 } 205 if (expr->value.str.len==0) { /* optimize away empty strings */ 206 if (use_copy) { 207 zval_dtor(expr); 208 } 209 return 0; 210 } 211 write_func(expr->value.str.val, expr->value.str.len); 212 if (use_copy) { 213 zval_dtor(expr); 214 } 215 return expr->value.str.len; 216 }
ふーん。ふーん。ふーん。そうですか、にやり。勝利は目前だっ!
「zend_make_printable_zval」関数で気に入らない文字列を成形している事が分かったので、149行目にある「IS_NULL」の条件分岐内容に従って出力したくない箇所に適用してみます。
実際にパッチを作成。
% vi php-4.4.4_Zend-no-output.patch ここから >>--------------------------------------------------------------------- --- Zend/zend.c,orig 2006-01-01 22:46:49.000000000 +0900 +++ Zend/zend.c 2006-10-27 11:43:35.789616350 +0900 @@ -149,27 +149,27 @@ case IS_NULL: expr_copy->value.str.len = 0; expr_copy->value.str.val = empty_string; + zend_error(E_NOTICE,"Can not output NULL"); break; case IS_BOOL: - if (expr->value.lval) { - expr_copy->value.str.len = 1; - expr_copy->value.str.val = estrndup("1", 1); - } else { - expr_copy->value.str.len = 0; - expr_copy->value.str.val = empty_string; - } + expr_copy->value.str.len = 0; + expr_copy->value.str.val = empty_string; + zend_error(E_NOTICE,"Can not output boolean"); break; case IS_RESOURCE: - expr_copy->value.str.val = (char *) emalloc(sizeof("Resource id #")-1 + MAX_LENGTH_OF_LONG); - expr_copy->value.str.len = sprintf(expr_copy->value.str.val, "Resource id #%ld", expr->value.lval); + expr_copy->value.str.val = empty_string; + expr_copy->value.str.len = 0; + zend_error(E_NOTICE,"Can not output Resource"); break; case IS_ARRAY: - expr_copy->value.str.len = sizeof("Array")-1; - expr_copy->value.str.val = estrndup("Array", expr_copy->value.str.len); + expr_copy->value.str.len = 0; + expr_copy->value.str.val = empty_string; + zend_error(E_NOTICE,"Can not output Array"); break; case IS_OBJECT: - expr_copy->value.str.len = sizeof("Object")-1; - expr_copy->value.str.val = estrndup("Object", expr_copy->value.str.len); + expr_copy->value.str.len = 0; + expr_copy->value.str.val = empty_string; + zend_error(E_NOTICE,"Can not output Object"); break; case IS_DOUBLE: *expr_copy = *expr; ---------------------------------------------------------------------<< ここまで
ついでに「NULL」「BOOL」「RESOURCE」「ARRAY」「OBJECT」を出力しようとした場合は「E_NOTICE」を発生させるようにしてみた。
作成したら、パッチを当ててバイナリを再ビルド。
% patch -p0 < php-4.4.4_Zend-no-output.patch patching file Zend/zend.c % make distclean % ./configure --disable-all --enable-debug % make
ビルドが完了したら、再度、処理を実行。
% sapi/cli/php -r ' \ $PTR = fopen( "configure", "r" ); \ echo null; echo "\n"; \ echo false; echo "\n"; \ echo true; echo "\n"; \ echo $PTR; echo "\n"; \ echo new stdClass; echo "\n"; \ echo array( ); echo "\n"; '; 結果 ----
わ〜い!これで、また一つ理解を深める事ができました。めでたし、めでたし。
所要時間は20分くらいですかね。段々と縮まってきたかな?