php-srcのコードリーディングした内容をコツコツ残すテスト。その2
opcodeの確認
OPcacheを使う方法
$ sapi/cli/php -dopcache.enable_cli=1 \
-dopcache.opt_debug_level=0x10000 \
-dzend_extension=$(pwd)/modules/opcache.so \
hoge.php
$_main:
; (lines=2, args=0, vars=0, tmps=0)
; (before optimizer)
; /path/to/hoge.php:1-4
; return [] RANGE[0..0]
0000 ECHO int(123)
0001 RETURN int(1)
phpdbgを使う方法
$ ./sapi/phpdbg/phpdbg '-p*' hoge.php PHP-8.1.4 ⬆ ✭ ✱ ◼
$_main:
; (lines=2, args=0, vars=0, tmps=0)
; /path/to/hoge.php:1-4
L0003 0000 ECHO int(123)
L0004 0001 RETURN int(1)
[Script ended normally]
ECHOのハンドラーを見てみる
今回デバッグするスクリプト
<?php
echo "hoge";
opcodeはこんな感じ
$_main:
; (lines=2, args=0, vars=0, tmps=0)
; (before optimizer)
; /path/to/echo.php:1-4
; return [] RANGE[0..0]
0000 ECHO string("hoge")
0001 RETURN int(1)
ECHO opcodeを詳しく見てみる。
まずはgdbでデバッグ
$ gdb --args sapi/cli/php fuga.php
(gdb) b execute_ex
(gdb) r
で、↓あたりまでnextする
if (UNEXPECTED((ret = ((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)) != 0)) {
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_vm_execute.h#L56971:L56971
execute_data->opline->handler
が各opcode(opline)のハンドラー
p execute_data->opline->handler
(gdb) $2 = (const void *) 0x10057ec90 <ZEND_ECHO_SPEC_CONST_HANDLER>
ZEND_ECHO_SPEC_CONST_HANDLERがhandlerになっている。
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ECHO_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zval *z;
SAVE_OPLINE();
z = RT_CONSTANT(opline, opline->op1);
if (Z_TYPE_P(z) == IS_STRING) {
zend_string *str = Z_STR_P(z);
if (ZSTR_LEN(str) != 0) {
zend_write(ZSTR_VAL(str), ZSTR_LEN(str));
}
} else {
// string以外
// ...
}
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
USE_OPLINE, SAVE_OPLINEはoplineを定義したり、oplineをexecute_dataに待避するマクロ
# define USE_OPLINE const zend_op *opline = EX(opline);
# define SAVE_OPLINE() EX(opline) = opline
まずはRT_CONSTANTで定数のzvalを取得している。
# define RT_CONSTANT(opline, node) \
((zval*)(((char*)(opline)) + (int32_t)(node).constant))
定数はoplineのポインタからnode.constant分移動した先にある。 このポインタの差分は pass_two() 内で設定している。
Z_TYPE_Pはzvalのtypeを取得している。
#define Z_TYPE_P(zval_p) Z_TYPE(*(zval_p))
#define Z_TYPE(zval) zval_get_type(&(zval))
static zend_always_inline zend_uchar zval_get_type(const zval* pz) {
return pz->u1.v.type;
}
マクロを展開すると z->u1.v.type
を見ていることになる。
(gdb) p z->u1.v.type
$6 = 6 '\006'
IS_STRING(6)と比較してSTRINGであればifの分岐の処理に入る。
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_types.h#L529:L529
Z_STR_Pでzend_stringを取得する。
#define Z_STR(zval) (zval).value.str
#define Z_STR_P(zval_p) Z_STR(*(zval_p))
(gdb) p *z.value.str
$10 = {gc = {refcount = 2, u = {type_info = 342}}, h = 9223372043240078536, len = 4, val = "h"}
ZSTR_VAL, ZSTR_LENはそれぞれzend_stringのvalueとlengthを取り出す
#define ZSTR_VAL(zstr) (zstr)->val
#define ZSTR_LEN(zstr) (zstr)->len
取り出したvalueとlengthを引数にzend_write()を呼び出している。zend_write()は後ほど説明。
IS_STRINGじゃない場合はこんな感じなコードになっている。
zend_string *str = zval_get_string_func(z);
if (ZSTR_LEN(str) != 0) {
zend_write(ZSTR_VAL(str), ZSTR_LEN(str));
} else if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(z) == IS_UNDEF)) {
ZVAL_UNDEFINED_OP1();
}
zend_string_release_ex(str, 0);
zval_get_string_func()
は __zval_get_string_func()
を呼び出す
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_operators.c#L906-L939
typeごとに分岐してよしなに変換していて
echo true; // => 1
echo false; // => 何も出力されない
echo []; // => WARNINGが出てArrayが表示
となる理由がわかる。
falseは ZSTR_EMPTY_ALLOC()
を返し、trueは ZSTR_CHAR('1')
を返す。
ZSTR_EMPTY_ALLOC()
は zend_empty_string
を返し、 zend_empty_string
はここで定義されている
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_string.c#L102-L104
zend_write()
zend_writeは関数ポインタで utility_functions->write_function
がセットされる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend.c#L879:L879
utility_functions->write_function
は php_output_write
がセットされる
https://github.com/php/php-src/blob/PHP-8.1.4/main/main.c#L2078:L2078
php_output_write
は php_output_op()
を呼び出す
https://github.com/php/php-src/blob/PHP-8.1.4/main/output.c#L261
php_output_op()
は sapi_module.ub_write()
を呼び出す
https://github.com/php/php-src/blob/PHP-8.1.4/main/output.c#L1083:L1083
SAPIごとにub_write()の処理を変えられる。CLIであれは標準出力に出力するし、mod_phpであればレスポンスボディとして出力する。
CLIのub_write()はwrite()を呼び出して標準出力に出力しているのはここでわかる
https://github.com/php/php-src/blob/PHP-8.1.4/sapi/cli/php_cli.c#L264
mod_phpはこんな感じ
https://github.com/php/php-src/blob/PHP-8.1.4/sapi/apache2handler/sapi_apache2.c#L71-L84