如何使用PHP Embed SAPI实现Opcodes查看器
PHP提供了一个EmbedSAPI,也就是说,PHP容许你在C/C++语言中调用PHP/ZE提供的函数。本文就通过基于EmbedSAPI实现一个PHP的opcodes查看器。
首先,下载PHP源码以供编译,我现在使用的是PHP5.3alpha2
进入源码目录:
./configure--enable-embed--with-config-file-scan-dir=/etc/php.d--with-mysql --with-config-file-path=/etc/
./make
./makeinstall
最后,记得要将生成的libphp5.so复制到运行时库的目录,我直接拷贝到了/lib/,否则会在运行你自己的embed程序的时候报错:
./embed:errorwhileloadingsharedlibraries:libphp5.so:cannotopensharedobjectfile:Nosuchfileordirectory
如果你对PHP的SAPI还不熟悉的话,我建议你看看我的这篇文章:深入理解ZendSAPIs(ZendSAPIInternals)
这个时候,你就可以在你的C代码中,嵌入PHP脚本解析器了,我的例子:
#include"sapi/embed/php_embed.h" intmain(intargc,char*argv[]){ PHP_EMBED_START_BLOCK(argc,argv); char*script="print'HelloWorld!';"; zend_eval_string(script,NULL, "SimpleHelloWorldApp"TSRMLS_CC); PHP_EMBED_END_BLOCK(); return0; }
然后就是要指明includepath了,一个简单的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)-oembedembed.cpp$(CFLAGS)$(LDFLAGS)
编译成功以后,运行,我们可以看到,stdout输出HelloWorld!
基于这个,我们就可以很容易的实现一个类似于vld的Opcodesdumper:
首先我们定义opcode的转换函数(全部的opcodes可以查看Zend/zend_vm_opcodes.h);
char*opname(zend_ucharopcode){ switch(opcode){ caseZEND_NOP:return"ZEND_NOP";break; caseZEND_ADD:return"ZEND_ADD";break; caseZEND_SUB:return"ZEND_SUB";break; caseZEND_MUL:return"ZEND_MUL";break; caseZEND_DIV:return"ZEND_DIV";break; caseZEND_MOD:return"ZEND_MOD";break; caseZEND_SL:return"ZEND_SL";break; caseZEND_SR:return"ZEND_SR";break; caseZEND_CONCAT:return"ZEND_CONCAT";break; caseZEND_BW_OR:return"ZEND_BW_OR";break; caseZEND_BW_AND:return"ZEND_BW_AND";break; caseZEND_BW_XOR:return"ZEND_BW_XOR";break; caseZEND_BW_NOT:return"ZEND_BW_NOT";break; /*...省略....*/ default:return"UNKNOW";break;
然后定义zval和znode的输出函数:
char*format_zval(zval*z) { staticcharbuffer[BUFFER_LEN]; intlen; switch(z->type){ caseIS_NULL: return"NULL"; caseIS_LONG: caseIS_BOOL: snprintf(buffer,BUFFER_LEN,"%d",z->value.lval); returnbuffer; caseIS_DOUBLE: snprintf(buffer,BUFFER_LEN,"%f",z->value.dval); returnbuffer; caseIS_STRING: snprintf(buffer,BUFFER_LEN,"\"%s\"",z->value.str.val); returnbuffer; caseIS_ARRAY: caseIS_OBJECT: caseIS_RESOURCE: caseIS_CONSTANT: caseIS_CONSTANT_ARRAY: return""; default: return"unknown"; } } char*format_znode(znode*n){ staticcharbuffer[BUFFER_LEN]; switch(n->op_type){ caseIS_CONST: returnformat_zval(&n->u.constant); break; caseIS_VAR: snprintf(buffer,BUFFER_LEN,"$%d",n->u.var/sizeof(temp_variable)); returnbuffer; break; caseIS_TMP_VAR: snprintf(buffer,BUFFER_LEN,"~%d",n->u.var/sizeof(temp_variable)); returnbuffer; break; default: return""; break; } }
然后定义op_array的输出函数:
voiddump_op(zend_op*op,intnum){ 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)); } voiddump_op_array(zend_op_array*op_array){ if(op_array){ inti; 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); } } }
最后,就是程序的主函数了:
intmain(intargc,char**argv){ zend_op_array*op_array; zend_file_handlefile_handle; if(argc!=2){ printf("usage:op_dumper<script>\n"); return1; } 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_INCLUDETSRMLS_CC); if(!op_array){ printf("Errorparsingscript:%s\n",file_handle.filename); return1; } dump_op_array(op_array); PHP_EMBED_END_BLOCK(); return0; }
编译,运行测试脚本(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
呵呵,怎么样,是不是很好玩呢?