ます’s Blog - どうでもいい記事100選

どうでもいい記事100選

無理して出力しようとするのは良くないと思うアレコレ

以前、「未定義の定数の扱い」というエントリで気に入らない挙動を何とかしたのですが、その続き。
どういった挙動が気に入らないかは、最初の実行結果を確認して頂くという事で。
まずは、玉(アーカイブ)を展開してディレクトリを移動。その後、バイナリをビルド。

% 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分くらいですかね。段々と縮まってきたかな?