中国青基会

查看完整版本: 深入理解PHP--zend最终调用函数分析

hew 2009-5-27 16:37

深入理解PHP--zend最终调用函数分析

[size=4][color=Blue]PHP源代码分析-zend最终调用函数分析[/color]
原文出处:[url]http://jackywdx.cn/2009/05/notes_of_zend_kernel[/url][/size]

在追踪zend 内核的时候发现最终调用的函数都在zend_vm_execute.h文件里面定义了,也就是说基本每一个PHP的语句,比如定义一个函数、echo一个表达式等任何一个语句最后的实现都是在zend_vm_execute.h里面定义了,
在zend.c文件zend_startup()函数里面调用了zend_init_opcodes_handlers(),初始化了labels数组,这个数组的类型是opcode_handler_t,这个类型在zend_compile.h文件里面定义了:
[table=95%][tr][td][font=FixedSys][color=#000000][color=#0000FF]typedef[/color] [color=#0000FF]int[/color] [color=#0000CC]([/color][color=#0000CC]*[/color]opcode_handler_t[color=#0000CC])[/color] [color=#0000CC]([/color]ZEND_OPCODE_HANDLER_ARGS[color=#0000CC])[/color][color=#0000CC];[/color]
[color=#FF9900]//在zend_vm_execute.h引用zend_opcode_handlers,
[/color]
[color=#FF9900]//zend_opcode_handlers = (opcode_handler_t*)labels;
[/color]
[color=#0000FF]extern[/color] ZEND_API opcode_handler_t [color=#0000CC]*[/color]zend_opcode_handlers[color=#0000CC];[/color]
[/color][/font][/td][/tr][/table]

opcode_handler_t是函数指针,指定一个用来处理每一个opcode的函数的入口地址,这些地址都保存在labels数组里面了。

[table=95%][tr][td][font=FixedSys][color=#000000][color=#0000FF]static[/color] [color=#0000FF]const[/color] opcode_handler_t labels[color=#0000CC][[/color][color=#0000CC]][/color] [color=#0000CC]=[/color] [color=#0000CC]{[/color]
        ZEND_NOP_SPEC_HANDLER[color=#0000CC],[/color]
      ZEND_NOP_SPEC_HANDLER[color=#0000CC],[/color]
        [color=#FF9900]//中间省略。。。。。
[/color]
        ZEND_NOP_SPEC_HANDLER[color=#0000CC],[/color]
      ZEND_ADD_SPEC_CONST_CONST_HANDLER[color=#0000CC],[/color]
      ZEND_ADD_SPEC_CONST_TMP_HANDLER[color=#0000CC],[/color]
      ZEND_ADD_SPEC_CONST_VAR_HANDLER[color=#0000CC],[/color]
        。。。。。。。
[color=#0000CC]}[/color][/color][/font][/td][/tr][/table]

这个结构体包含了近3776个成员,这些成员都是函数名称,每次执行一个opcode的时候都要到这个labels里面找对应的handler处理函数。
在zend_vm_execute.h文件最后的有两个函数:

[table=95%][tr][td][font=FixedSys][color=#000000][color=#0000FF]static[/color] opcode_handler_t zend_vm_get_opcode_handler[color=#0000CC]([/color]zend_uchar opcode[color=#0000CC],[/color] zend_op[color=#0000CC]*[/color] op[color=#0000CC])[/color]
[color=#0000CC]{[/color]
        [color=#0000FF]static[/color] [color=#0000FF]const[/color] [color=#0000FF]int[/color] zend_vm_decode[color=#0000CC][[/color][color=#0000CC]][/color] [color=#0000CC]=[/color] [color=#0000CC]{[/color]
            _UNUSED_CODE[color=#0000CC],[/color] [color=#FF9900]/* 0              */[/color]
            _CONST_CODE[color=#0000CC],[/color]  [color=#FF9900]/* 1 = IS_CONST   */[/color]
            _TMP_CODE[color=#0000CC],[/color]    [color=#FF9900]/* 2 = IS_TMP_VAR */[/color]
            _UNUSED_CODE[color=#0000CC],[/color] [color=#FF9900]/* 3              */[/color]
            _VAR_CODE[color=#0000CC],[/color]    [color=#FF9900]/* 4 = IS_VAR     */[/color]
            _UNUSED_CODE[color=#0000CC],[/color] [color=#FF9900]/* 5              */[/color]
            _UNUSED_CODE[color=#0000CC],[/color] [color=#FF9900]/* 6              */[/color]
            _UNUSED_CODE[color=#0000CC],[/color] [color=#FF9900]/* 7              */[/color]
            _UNUSED_CODE[color=#0000CC],[/color] [color=#FF9900]/* 8 = IS_UNUSED  */[/color]
            _UNUSED_CODE[color=#0000CC],[/color] [color=#FF9900]/* 9              */[/color]
            _UNUSED_CODE[color=#0000CC],[/color] [color=#FF9900]/* 10             */[/color]
            _UNUSED_CODE[color=#0000CC],[/color] [color=#FF9900]/* 11             */[/color]
            _UNUSED_CODE[color=#0000CC],[/color] [color=#FF9900]/* 12             */[/color]
            _UNUSED_CODE[color=#0000CC],[/color] [color=#FF9900]/* 13             */[/color]
            _UNUSED_CODE[color=#0000CC],[/color] [color=#FF9900]/* 14             */[/color]
            _UNUSED_CODE[color=#0000CC],[/color] [color=#FF9900]/* 15             */[/color]
            _CV_CODE      [color=#FF9900]/* 16 = IS_CV     */[/color]
        [color=#0000CC]}[/color][color=#0000CC];[/color]
        [color=#FF9900]//这句很关键,就是返回处理当前opcode的handler,
[/color]
                [color=#FF9900]//zend_opcode_handlers等于上面定义的lables
[/color]
        [color=#0000FF]return[/color] zend_opcode_handlers[color=#0000CC][[/color]opcode [color=#0000CC]*[/color] 25 [color=#0000CC]+[/color] zend_vm_decode[color=#0000CC][[/color]op[color=#0000CC]-[/color][color=#0000CC]>[/color]op1[color=#0000CC].[/color]op_type[color=#0000CC]][/color] [color=#0000CC]*[/color] 5 [color=#0000CC]+[/color] zend_vm_decode[color=#0000CC][[/color]op[color=#0000CC]-[/color][color=#0000CC]>[/color]op2[color=#0000CC].[/color]op_type[color=#0000CC]][/color][color=#0000CC]][/color][color=#0000CC];[/color]
[color=#0000CC]}[/color]

[color=#FF9900]//设置zend_op里面的handler,指定处理的函数
[/color]
ZEND_API [color=#0000FF]void[/color] zend_vm_set_opcode_handler[color=#0000CC]([/color]zend_op[color=#0000CC]*[/color] op[color=#0000CC])[/color]
[color=#0000CC]{[/color]
    op[color=#0000CC]-[/color][color=#0000CC]>[/color]handler [color=#0000CC]=[/color] zend_vm_get_opcode_handler[color=#0000CC]([/color]zend_user_opcodes[color=#0000CC][[/color]op[color=#0000CC]-[/color][color=#0000CC]>[/color]opcode[color=#0000CC]][/color][color=#0000CC],[/color] op[color=#0000CC])[/color][color=#0000CC];[/color]
[color=#0000CC]}[/color][/color][/font][/td][/tr][/table]

如果想知道每个opcode是交给哪个handler处理,可以在
return zend_opcode_handlers[opcode * 25 + zend_vm_decode[op->op1.op_type] * 5 + zend_vm_decode[op->op2.op_type]];
这条语句前面打印出索引值:

[table=95%][tr][td][font=FixedSys][color=#000000][color=#0000FF]int[/color] index[color=#0000CC];[/color]
index [color=#0000CC]=[/color] opcode [color=#0000CC]*[/color] 25 [color=#0000CC]+[/color] zend_vm_decode[color=#0000CC][[/color]op[color=#0000CC]-[/color][color=#0000CC]>[/color]op1[color=#0000CC].[/color]op_type[color=#0000CC]][/color] [color=#0000CC]*[/color] 5 [color=#0000CC]+[/color] zend_vm_decode[color=#0000CC][[/color]op[color=#0000CC]-[/color][color=#0000CC]>[/color]op2[color=#0000CC].[/color]op_type[color=#0000CC]][/color][color=#0000CC];[/color]
[color=#FF0000]printf[/color][color=#0000CC]([/color][color=#FF00FF]"zend_opcode_handlers_index:%d\n"[/color][color=#0000CC],[/color] index[color=#0000CC])[/color][color=#0000CC];[/color][/color][/font][/td][/tr][/table]

然后再重新 make && make install
编写一个test.php文件:
<?php
$a = "test";
?>
再运行/usr/local/php5.2.9/bin/php  test.php
输出的结果如下:
zend_opcode_handlers_index:970
zend_opcode_handlers_index:1553
zend_opcode_handlers_index:3743
简单的一个赋值语句,最后执行了三个函数,这三个数字代表在labels里面的偏移量,找到labels里面第970个值:
ZEND_ASSIGN_SPEC_CV_CONST_HANDLER
再找到这个函数:

[table=95%][tr][td][font=FixedSys][color=#000000][color=#0000FF]static[/color] [color=#0000FF]int[/color] ZEND_ASSIGN_SPEC_CV_CONST_HANDLER[color=#0000CC]([/color]ZEND_OPCODE_HANDLER_ARGS[color=#0000CC])[/color]
[color=#0000CC]{[/color]
    zend_op [color=#0000CC]*[/color]opline [color=#0000CC]=[/color] EX[color=#0000CC]([/color]opline[color=#0000CC])[/color][color=#0000CC];[/color]

    zval [color=#0000CC]*[/color]value [color=#0000CC]=[/color] [color=#0000CC]&[/color]opline[color=#0000CC]-[/color][color=#0000CC]>[/color]op2[color=#0000CC].[/color]u[color=#0000CC].[/color]constant[color=#0000CC];[/color]

     zend_assign_to_variable[color=#0000CC]([/color][color=#0000CC]&[/color]opline[color=#0000CC]-[/color][color=#0000CC]>[/color]result[color=#0000CC],[/color] [color=#0000CC]&[/color]opline[color=#0000CC]-[/color][color=#0000CC]>[/color]op1[color=#0000CC],[/color] [color=#0000CC]&[/color]opline[color=#0000CC]-[/color][color=#0000CC]>[/color]op2[color=#0000CC],[/color] value[color=#0000CC],[/color] [color=#0000CC]([/color]0[color=#0000CC]?[/color]IS_TMP_VAR[color=#0000CC]:[/color]IS_CONST[color=#0000CC])[/color][color=#0000CC],[/color] EX[color=#0000CC]([/color]Ts[color=#0000CC])[/color] TSRMLS_CC[color=#0000CC])[/color][color=#0000CC];[/color]
    [color=#FF9900]/* zend_assign_to_variable() always takes care of op2, never free it! */[/color]

    ZEND_VM_NEXT_OPCODE[color=#0000CC]([/color][color=#0000CC])[/color][color=#0000CC];[/color]
[color=#0000CC]}[/color][/color][/font][/td][/tr][/table]

这个函数主要是对变量进行赋值,把值存进opline->result里面。后面1553和3743偏移值分别是
ZEND_RETURN_SPEC_CONST_HANDLER
ZEND_HANDLE_EXCEPTION_SPEC_HANDLER
分别是用来处理函数返回和异常,每个PHP文件执行到最后都会自动执行这两个函数。

另外想要快速查找每个偏移量对应的是哪个函数,可以先把  static const opcode_handler_t labels[] 里面的3776个成员保存在一个文件opcode_handler.txt,然后写个函数来智能处理:

[table=95%][tr][td][font=FixedSys][color=#000000]
[color=#0000CC]#[/color][color=#FF0000]include[/color][color=#0000CC]<[/color]stdio[color=#0000CC].[/color]h[color=#0000CC]>[/color]
[color=#0000CC]#[/color][color=#FF0000]include[/color][color=#0000CC]<[/color]stdlib[color=#0000CC].[/color]h[color=#0000CC]>[/color]
[color=#0000FF]int[/color] main[color=#0000CC]([/color][color=#0000FF]int[/color] c[color=#0000CC],[/color] [color=#0000FF]char[/color] [color=#0000CC]*[/color]v[color=#0000CC][[/color][color=#0000CC]][/color][color=#0000CC])[/color][color=#0000CC]{[/color]
   [color=#FF9900]//在labels的偏移量
[/color]
    [color=#0000FF]int[/color] offset [color=#0000CC]=[/color] [color=#FF0000]atoi[/color][color=#0000CC]([/color]v[color=#0000CC][[/color]1[color=#0000CC]][/color][color=#0000CC])[/color][color=#0000CC];[/color]
    [color=#FF0000]FILE[/color] [color=#0000CC]*[/color]fp[color=#0000CC];[/color]
    fp [color=#0000CC]=[/color] [color=#FF0000]fopen[/color][color=#0000CC]([/color][color=#FF00FF]"/home/dexin/op_code_handler.txt"[/color][color=#0000CC],[/color][color=#FF00FF]"r"[/color][color=#0000CC])[/color][color=#0000CC];[/color]
    [color=#0000FF]char[/color] handler[color=#0000CC][[/color]200[color=#0000CC]][/color][color=#0000CC];[/color]
    [color=#0000FF]int[/color] i [color=#0000CC]=[/color] 0[color=#0000CC];[/color]
    [color=#0000FF]while[/color][color=#0000CC]([/color][color=#0000CC]![/color][color=#FF0000]feof[/color][color=#0000CC]([/color]fp[color=#0000CC])[/color][color=#0000CC])[/color][color=#0000CC]{[/color]
        [color=#FF0000]fgets[/color][color=#0000CC]([/color]handler[color=#0000CC],[/color]1024[color=#0000CC],[/color]fp[color=#0000CC])[/color][color=#0000CC];[/color]
        [color=#0000FF]if[/color][color=#0000CC]([/color]i [color=#0000CC]![/color][color=#0000CC]=[/color] offset[color=#0000CC])[/color][color=#0000CC]{[/color]
            i[color=#0000CC]+[/color][color=#0000CC]+[/color][color=#0000CC];[/color]
        [color=#0000CC]}[/color][color=#0000FF]else[/color][color=#0000CC]{[/color]
            [color=#FF0000]printf[/color][color=#0000CC]([/color][color=#FF00FF]"opcode handler:%s\n"[/color][color=#0000CC],[/color] handler[color=#0000CC])[/color][color=#0000CC];[/color]
            [color=#0000FF]break[/color][color=#0000CC];[/color]
        [color=#0000CC]}[/color]
   
    [color=#0000CC]}[/color]
    [color=#0000FF]return[/color] 1[color=#0000CC];[/color]
[color=#0000CC]}[/color][/color][/font][/td][/tr][/table]

编译:gcc -g -o gethandler gethandler.c
然后把gethandler 放入/bin/gethandler就可以了,比如我想查询偏移量为970的handler:
gethandler 970
结果:opcode handler:         ZEND_ASSIGN_SPEC_CV_CONST_HANDLER,
再去找到这个函数的实现就好了。
写完之后发现用shell写更加快:
#gethandler_awk
#!/bin/bash
awk 'NR == offset {print "opcode handler:"$0} offset=$1 /home/dexin/op_code_handler.txt

chmod+x gethandler_awk
放入/bin/gethandler_awk
gethandler_awk 970
同样可以得出结果
opcode handler:         ZEND_ASSIGN_SPEC_CV_CONST_HANDLER,
页: [1]
查看完整版本: 深入理解PHP--zend最终调用函数分析
Baidu