如何使用PHP Embed SAPI实现Opcodes查看器
php提供了一个embed sapi,也就是说,php容许你在c/c++语言中调用php/ze提供的函数。本文就通过基于embed sapi实现一个php的opcodes查看器。
首先,下载php源码以供编译, 我现在使用的是php5.3 alpha2
进入源码目录:
./configure --enable-embed --with-config-file-scan-dir=/etc/php.d --with-mysql --with-config-file-path=/etc/
./make
./make install
最后,记得要将生成的libphp5.so复制到运行时库的目录,我直接拷贝到了/lib/, 否则会在运行你自己的embed程序的时候报错:
./embed: error while loading shared libraries: libphp5.so: cannot open shared object file: no such file or directory
如果你对php的sapi还不熟悉的话,我建议你看看我的这篇文章:深入理解zend sapis(zend sapi internals)
这个时候,你就可以在你的c代码中,嵌入php脚本解析器了, 我的例子:
#include "sapi/embed/php_embed.h" int main(int argc, char * argv[]){ php_embed_start_block(argc,argv); char * script = " print 'hello world!';"; zend_eval_string(script, null, "simple hello world app" tsrmls_cc); php_embed_end_block(); return 0; }
然后就是要指明include path了,一个简单的makefile
cc = gcc cflags = -i/usr/local/include/php/ \ -i/usr/local/include/php/main \ -i/usr/local/include/php/zend \ -i/usr/local/include/php/tsrm \ -wall -g ldflags = -lstdc++ -l/usr/local/lib -lphp5 all: $(cc) -o embed embed.cpp $(cflags) $(ldflags)
编译成功以后, 运行,我们可以看到, stdout输出 hello world!
基于这个,我们就可以很容易的实现一个类似于vld的opcodes dumper:
首先我们定义opcode的转换函数(全部的opcodes可以查看zend/zend_vm_opcodes.h);
char *opname(zend_uchar opcode){ switch(opcode) { case zend_nop: return "zend_nop"; break; case zend_add: return "zend_add"; break; case zend_sub: return "zend_sub"; break; case zend_mul: return "zend_mul"; break; case zend_div: return "zend_div"; break; case zend_mod: return "zend_mod"; break; case zend_sl: return "zend_sl"; break; case zend_sr: return "zend_sr"; break; case zend_concat: return "zend_concat"; break; case zend_bw_or: return "zend_bw_or"; break; case zend_bw_and: return "zend_bw_and"; break; case zend_bw_xor: return "zend_bw_xor"; break; case zend_bw_not: return "zend_bw_not"; break; /*...省略 ....*/ default : return "unknow"; break;
然后定义zval和znode的输出函数:
char *format_zval(zval *z) { static char buffer[buffer_len]; int len; switch(z->type) { case is_null: return "null"; case is_long: case is_bool: snprintf(buffer, buffer_len, "%d", z->value.lval); return buffer; case is_double: snprintf(buffer, buffer_len, "%f", z->value.dval); return buffer; case is_string: snprintf(buffer, buffer_len, "\"%s\"", z->value.str.val); return buffer; case is_array: case is_object: case is_resource: case is_constant: case is_constant_array: return ""; default: return "unknown"; } } char * format_znode(znode *n){ static char buffer[buffer_len]; switch (n->op_type) { case is_const: return format_zval(&n->u.constant); break; case is_var: snprintf(buffer, buffer_len, "$%d", n->u.var/sizeof(temp_variable)); return buffer; break; case is_tmp_var: snprintf(buffer, buffer_len, "~%d", n->u.var/sizeof(temp_variable)); return buffer; break; default: return ""; break; } }
然后定义op_array的输出函数:
void dump_op(zend_op *op, int num){ printf("%5d %5d %30s %040s %040s %040s\n", num, op->lineno, opname(op->opcode), format_znode(&op->op1), format_znode(&op->op2), format_znode(&op->result)) ; } void dump_op_array(zend_op_array *op_array){ if(op_array) { int i; printf("%5s %5s %30s %040s %040s %040s\n", "opnum", "line", "opcode", "op1", "op2", "result"); for(i = 0; i < op_array->last; i++) { dump_op(&op_array->opcodes[i], i); } } }
最后,就是程序的主函数了:
int main(int argc, char **argv){ zend_op_array *op_array; zend_file_handle file_handle; if(argc != 2) { printf("usage: op_dumper <script>\n"); return 1; } php_embed_start_block(argc,argv); printf("script: %s\n", argv[1]); file_handle.filename = argv[1]; file_handle.free_filename = 0; file_handle.type = zend_handle_filename; file_handle.opened_path = null; op_array = zend_compile_file(&file_handle, zend_include tsrmls_cc); if(!op_array) { printf("error parsing script: %s\n", file_handle.filename); return 1; } dump_op_array(op_array); php_embed_end_block(); return 0; }
编译,运行测试脚本(sample.php):
sample.php:
echo "laruence";
命令:
./opcodes_dumper sample.php
得到输出结果(如果你对下面的结果很迷惑,那么建议你再看看我的这篇文章:深入理解php原理之opcodes):
script: sample.php
opnum line opcode op1 op2 result
0 2 zend_echo "laruence"
1 4 zend_return 1
呵呵,怎么样,是不是很好玩呢?
上一篇: php+mysql实现无限级分类
下一篇: 大家觉得香椿臭么