找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 893|回复: 0
打印 上一主题 下一主题

如何使用PHP Embed SAPI实现Opcodes查看器

[复制链接]

2588

主题

2588

帖子

7694

积分

论坛元老

Rank: 8Rank: 8

积分
7694
跳转到指定楼层
楼主
发表于 2018-2-14 05:41:39 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

            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 last; i++) {
   dump_op(&op_array->opcodes, i);
  }
}
}
最后,就是程序的主函数了:
int main(int argc, char **argv){
zend_op_array *op_array;
zend_file_handle file_handle;
if(argc != 2) {
  printf("usage: op_dumper \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):
[U]复制代码[/U] 代码如下:
sample.php:
   echo "laruence";
命令:
[U]复制代码[/U] 代码如下:
./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 OPCode缓存 APC详细介绍
  • 理解php原理的opcodes(操作码)
  • 利用PHP扩展vld查看PHP opcode操作步骤
  • 为PHP5.4开启Zend OPCode缓存
  • PHP内核学习教程之php opcode内核实现
  • 深入理解PHP之OpCode原理详解
            
  • 分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    用户反馈
    客户端