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脚本解析器了, 我的例子:
1
2
3
4
5
6
7
8
9
|
# 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
1
2
3
4
5
6
7
8
9
|
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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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的输出函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
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的输出函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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);
}
}
}
|
最后,就是程序的主函数了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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
呵呵,怎么样,是不是很好玩呢?