引言
在python中有一个库Construct ,可以用来解析二进制数据,用这个工具分析网络包,带格式的数据文件等等很方便。
前一阵子分析sqlite数据库文件格式时要是用这种工具,也可以省不少事。
不过Construct2.9与以前的版本,改动很大,原来用Construct写的python代码,基本上要重新改写一遍了。在查看Construct源码的过程中,发现Construct的基本实现思路是递归下降分析法。用对象的构造方法来动态地定义数据结构,在parse方法中实现对二进制数据的解析。
打算用php实现二进制数据的解析,但是在实现思路上与python的Construct完全不一样。
立即学习“PHP免费学习笔记(深入)”;
推荐PHP视频教程:https://www.php.cn/course/list/29/type/2.html
基本思路
由于python的Construct用python的对象语法来实现动态层次结构的定义与解析,受限于python的对象语法,有一些结构定义看起来很晦涩。
我的想法是定义一个小小的,专门用于描述动态层次结构的语言,这样一来就可以尽量的照顾人们常用的表达习惯。
以这个小项目来说,以C语言中的结构体定义语法为蓝本,在此基础上增加条件化,循环环的结构定义。
在二进制数据中有一种常见的结构,前面几个字节存放后面数据块的长度,接下来才是数据块。这种结构用C语言的结构体定义表示时并不方便。整个数据块的长度是变化的,在编译时无法确定,只能在解析时才能确定。因此有必要对C语言的结构体定义语法进行扩展。
第一步,实现对 C语言结构体的解析
在这一步,先不考虑动态的层次的结构体定义,而是实现这个小语言的最核心的部分,至少它要能理解C语言的结构体定义,并能根据这个结构体定义对二进制数据进行解析。这一步完成后再实现动态的结构体的定义与解析。
本项目基于前面文件中简单介绍过的ADOS脚本语言引擎。
先看一下我们的任务
这里有一个完全是C语言规格的结构体定义文件
blockStruct.h
struct student{ char name[2]; int num; int age; char addr[3];};struct teacher{ char name[2]; int num; char addr[3];};
登录后复制登录后复制
待解析的二进制数据块
"ABABCABABC"
登录后复制
希望得到的解析结果
[value] => Array ( [name] => AB [num] => 1 [age] => 2 [addr] => ABC )[value] => Array ( [name] => AB [num] => 1 [addr] => ABC )
登录后复制
感兴趣的朋友可以留意一下这三者之间的关系
下而是仅实现了对C语言结构体进行编译的词法规则文件
=/','_greq','>'],['/^/','_grea','>'],['/^','_less','下面是仅实现了对C语言结构体进行编译的语法规则文件
0){return $extraArray[0];}else{return '';}}//求出放在附加信息中的数组长度function elementSize($extraArray){if(count($extraArray)>0){return intval($extraArray[0]);}else{return 0;}}//处理源代码中的多条声明语句function handleStatementList($stack,$coder,$listIndex,$statementIndex){$t1= $this->topItem($stack,$statementIndex);if($listIndex>0){$t2= $this->topItem($stack,$listIndex);//将元素名所在项的附加信息数组找出来$extraArray=$t2[TokenExtraIndex];}else{ //$listIndex=0表示,statementList是上级节点,附加信息数组应该是空数组$extraArray=[];}//将statement中的附加信息添加到statementList的附加信息中去$extraArray[]=$t1[TokenExtraIndex];return ['#',$extraArray];}//处理源代码中的一条声明语句function handleStatement($stack,$coder,$typeItemIndex,$idenItemIndex){$t1= $this->topItem($stack,$typeItemIndex);$elementType = $t1[TokenValueIndex];$t2= $this->topItem($stack,$idenItemIndex);$elementName = $t2[TokenValueIndex];//将元素名所在项的附加信息数组找出来$extraArray=$t2[TokenExtraIndex];$valLen =$extraArray[0];//附加信息中包含 元素名称,元素类型,数据长度return [$elementName,[$elementName,$elementType,$valLen]];}//语法规则处理函数名由规则右边部分与规则左边部分拼接而成//语法规则定义的先后决定了归约时匹配的顺序,要根据实际的语法安排//如果不熟悉语法,随意调整语法规则的先后次序将有可能导致语法错误// struct list {{{function _structList_0_structList_struct($stack,$coder){$coder->pushBlockTail();return ['#',[]];}function _structList_0_struct($stack,$coder){$coder->pushBlockTail();return ['#',[]];}// struct list }}}// struct {{{function _struct_0_structName_blockStatement_semi($stack,$coder){$t1= $this->topItem($stack,3);$structName = $t1[TokenValueIndex];$t2= $this->topItem($stack,2);$extraArray=$t2[TokenExtraIndex];return [$structName,$extraArray];}// struct }}}// struct name {{{function _structName_0_strukey_iden($stack,$coder){ $t1= $this->topItem($stack,1);$structName = $t1[TokenValueIndex];$coder->pushBlockHeader($structName);return $this->pass($stack,1);}// struct name }}}// blockStatement {{{function _blockStatement_0_lcb_statementList_rcb($stack,$coder){return $this->pass($stack,2);}// blockStatement }}}// statement list {{{function _statementList_0_statementList_statement($stack,$coder){return $this->handleStatementList($stack,$coder,2,1);}function _statementList_0_statement($stack,$coder){//此处0表示statementList是上一级节点,要做特殊处理return $this->handleStatementList($stack,$coder,0,1);}// statement list }}}// statement {{{function _statement_0_double_term_semi($stack,$coder){$t1= $this->topItem($stack,2);$elementName = $t1[TokenValueIndex];$parseFuncName = 'parseDouble';$coder->pushBlockBody($parseFuncName,$elementName);return $this->handleStatement($stack,$coder,3,2);}function _statement_0_float_term_semi($stack,$coder){$t1= $this->topItem($stack,2);$elementName = $t1[TokenValueIndex];$parseFuncName = 'parseFloat';$coder->pushBlockBody($parseFuncName,$elementName);return $this->handleStatement($stack,$coder,3,2);}function _statement_0_char_term_semi($stack,$coder){$t1= $this->topItem($stack,2);$elementName = $t1[TokenValueIndex];$size = $this->elementSize($t1[TokenExtraIndex]);$parseFuncName = 'parseFixStr';$coder->pushBlockBody($parseFuncName,$elementName,$size);return $this->handleStatement($stack,$coder,3,2);}function _statement_0_int_term_semi($stack,$coder){$t1= $this->topItem($stack,2);$elementName = $t1[TokenValueIndex];$parseFuncName = 'parseInt';$coder->pushBlockBody($parseFuncName,$elementName,4);return $this->handleStatement($stack,$coder,3,2);}// statement }}}// term {{{function _term_0_term_array($stack,$coder){$t1= $this->topItem($stack,1);$valLen = $t1[TokenValueIndex];//将数据长度放入附加信息 $t2= $this->topItem($stack,2);return [$t2[TokenValueIndex],[$valLen]];}function _term_0_iden($stack,$coder){$t1= $this->topItem($stack,1);//未指定数据长度时将长度值设为0 $valLen = '0';$t2= $this->topItem($stack,2);return [$t1[TokenValueIndex],[$valLen]];}// term }}}// array {{{function _array_0_lb_num_rb($stack,$coder){return $this->pass($stack,2);}// array }}}} // end of class登录后复制
下面是对基本数据类型,整数,字符串的解析函数(备注,用于实验,还没有覆盖全部的基本数据类型)
0){$raw = substr($data, 0,1);$value = unpack("C1",$raw)[1];return ['value'=>$value,'size'=>1,'error'=>0,'msg'=>'ok'];}else{return ['value'=>False,'size'=>0,'error'=>1,'msg'=>'not enough for parseByte'];}}//解析一个有符号整数function parseInt($context,$size = 4){if(!($size == 2 or $size == 4 or $size == 8 )){return ['value'=>False,'size'=>0,'error'=>2,'msg'=>'not a valid size'];}if($size == 2) $format = "s1";if($size == 4) $format = "l1";if($size == 8) $format = "q1";$pos=$context['pos'];$data = substr($context['data'], $pos);if(strlen($data)>=$size){$raw = substr($data, 0,$size);$value = unpack($format,$raw)[1];return ['value'=>$value,'size'=>$size,'error'=>0,'msg'=>'ok'];}else{return ['value'=>False,'size'=>0,'error'=>1,'msg'=>'no data for parseInt'];}}//解析一个大端无符号整数function parseBUInt($context,$size = 4){if(!($size == 2 or $size == 4 or $size == 8 )){return ['value'=>False,'size'=>0,'error'=>2,'msg'=>'not a valid size'];}if($size == 2) $format = "n1";if($size == 4) $format = "N1";if($size == 8) $format = "J1";$pos=$context['pos'];$data = substr($context['data'], $pos);if(strlen($data)>=$size){$raw = substr($data, 0,$size);$value = unpack($format,$raw)[1];return ['value'=>$value,'size'=>$size,'error'=>0,'msg'=>'ok'];}else{return ['value'=>False,'size'=>0,'error'=>1,'msg'=>'no data for parseBUInt'];}}//解析一个小端无符号整数function parseLUInt($context,$size = 4){if(!($size == 2 or $size == 4 or $size == 8 )){return ['value'=>False,'size'=>0,'error'=>2,'msg'=>'not a valid size'];}if($size == 2) $format = "v1";if($size == 4) $format = "NL";if($size == 8) $format = "P1";$pos=$context['pos'];$data = substr($context['data'], $pos);if(strlen($data)>=$size){$raw = substr($data, 0,$size);$value = unpack($format,$raw)[1];return ['value'=>$value,'size'=>$size,'error'=>0,'msg'=>'ok'];}else{return ['value'=>False,'size'=>0,'error'=>1,'msg'=>'no data for parseLUInt'];}}//解析一个null结束的字符串function parseString($context,$size=0){$pos=$context['pos'];$data = substr($context['data'], $pos);$p=0;$raw = substr($data, $p,1);$byte = unpack("C1",$raw)[1];$result ='';while($byte){$result.=$raw;$p++;if($p>=strlen($data)){return ['value'=>False,'size'=>0,'error'=>1,'msg'=>'not find null end for parseString'];}$raw = substr($data, $p,1);$byte = unpack("C1",$raw)[1];}return ['value'=>$result,'size'=>$p,'error'=>0,'msg'=>'ok'];}//解析一个定长字符串function parseFixStr($context,$size=0){$pos=$context['pos'];$data = substr($context['data'], $pos);//var_dump($data);if(strlen($data)>=$size){$result ='';for($i=0;$i$result,'size'=>$size,'error'=>0,'msg'=>'ok']; }return ['value'=>False,'size'=>0,'error'=>1,'msg'=>'not enough for parseFixedString'];}登录后复制
下面是用于模板替换的工作函数
<?php //用于进行模板替换的工作函数namespace Ados;defined('__STRUCT_PARSE_TEMP__') or define('__STRUCT_PARSE_TEMP__', './');define('_blockParsTempFile_',__STRUCT_PARSE_TEMP__.'parseFunc.template.php');//取出两个记号串之间的内容function strBetweenToke($src,$toke1,$toke2){$p1 = strpos($src,$toke1);$p2 = strpos($src,$toke2);return substr($src,$p1+strlen($toke1),$p2-$p1-strlen($toke1));}//取得块分析函数的headerfunction blockHeaderStr(){$src = file_get_contents(_blockParsTempFile_);return strBetweenToke($src,'/*blockHeader{{*/','/*blockHeader}}*/');}//取得块分析函数的bodyfunction blockBodyStr(){$src = file_get_contents(_blockParsTempFile_);return strBetweenToke($src,'/*blockBody{{*/','/*blockBody}}*/');}//取得块分析函数的tailfunction blockTailStr(){$src = file_get_contents(_blockParsTempFile_);return strBetweenToke($src,'/*blockTail{{*/','/*blockTail}}*/');}define('_blockHeaderStr_',blockHeaderStr());define('_blockBodyStr_',blockBodyStr());define('_blockTailStr_',blockTailStr());function makeBlockHeader($blockName){return str_replace('parseBlock_Temp', 'parse'.$blockName, _blockHeaderStr_);}function makeblockBody($parseFuncName,$filedName='',$filedSize=0){$tmp = str_replace('parseByte', $parseFuncName, _blockBodyStr_);$tmp = str_replace('$filedName', $filedName, $tmp);$tmp = str_replace('$filedSize', $filedSize, $tmp);return $tmp;}function makeblockTail(){return _blockTailStr_ ;}/*echo blockHeaderStr();echo blockBodyStr();echo blockTailStr();echo makeBlockHeader('Test1');echo makeblockBody('parseInt');echo makeblockBody('parseStr');echo makeblockTail();*/登录后复制
有了这些准备工作后,就可以实现一个语法制导的编码器,用于生成最终的解析函数。
下而就是一个简单的编码器,在对结构体定义进行语法分析时,生成可用于解析二进制数据的脚本代码engine = $engine;}else{ exit('the engine is not valid in StructwkrCoder construct.');}}//编译得到的最终结果public function codeLines(){if(count($this->codeLines)codeLines);$i+=1) {$script.=$this->codeLines[$i];}return $script;}//输出编译后的结果public function printCodeLines(){echo $this->codeLines();}//添加一个块解析函数头public function pushBlockHeader($structName){$structName=ucfirst($structName);$content = makeBlockHeader($structName);array_push($this->codeLines, $content);$lineIndex=$this->lineIndex;$this->lineIndex+=1;return $lineIndex;}//添加一个块解析函数体public function pushBlockBody($parseFuncName,$filedName='',$filedSize=0){$content = makeblockBody($parseFuncName,$filedName,$filedSize);array_push($this->codeLines, $content);$lineIndex=$this->lineIndex;$this->lineIndex+=1;return $lineIndex;}//添加一个块解析函数尾public function pushBlockTail(){$content = makeblockTail();array_push($this->codeLines, $content);$lineIndex=$this->lineIndex;$this->lineIndex+=1;return $lineIndex;}}登录后复制
自动生成的用于解析的脚本文件
对c语言的结构体进行编译,最终得到可以用于解析二进制数据的一系列函数,比如上述结构体定义文件中定义了两个结构体
struct student{ char name[2]; int num; int age; char addr[3];};struct teacher{ char name[2]; int num; char addr[3];};登录后复制登录后复制
那么就会生成与这两个结构体对应的解析函数parseStudent与parseTeacher。
下面就是自动生成的测试脚本
False,'size'=>0,'error'=>$expRes['error'],'msg'=>$expRes['msg']];}$expRes = parseInt($context,4);if($expRes['error']==0){$filed = 'num';if($filed){$valueArray[$filed]=$expRes['value'];}else{$valueArray[]=$expRes['value'];}$context['pos']+=$expRes['size'];$totalSize+= $expRes['size'];}else{return ['value'=>False,'size'=>0,'error'=>$expRes['error'],'msg'=>$expRes['msg']];}$expRes = parseInt($context,4);if($expRes['error']==0){$filed = 'age';if($filed){$valueArray[$filed]=$expRes['value'];}else{$valueArray[]=$expRes['value'];}$context['pos']+=$expRes['size'];$totalSize+= $expRes['size'];}else{return ['value'=>False,'size'=>0,'error'=>$expRes['error'],'msg'=>$expRes['msg']];}$expRes = parseFixStr($context,3);if($expRes['error']==0){$filed = 'addr';if($filed){$valueArray[$filed]=$expRes['value'];}else{$valueArray[]=$expRes['value'];}$context['pos']+=$expRes['size'];$totalSize+= $expRes['size'];}else{return ['value'=>False,'size'=>0,'error'=>$expRes['error'],'msg'=>$expRes['msg']];}return ['value'=>$valueArray,'size'=>$totalSize,'error'=>0,'msg'=>'ok'];}function parseTeacher($context,$size=0){$valueArray=[];$totalSize = 0;$expRes = parseFixStr($context,2);if($expRes['error']==0){$filed = 'name';if($filed){$valueArray[$filed]=$expRes['value'];}else{$valueArray[]=$expRes['value'];}$context['pos']+=$expRes['size'];$totalSize+= $expRes['size'];}else{return ['value'=>False,'size'=>0,'error'=>$expRes['error'],'msg'=>$expRes['msg']];}$expRes = parseInt($context,4);if($expRes['error']==0){$filed = 'num';if($filed){$valueArray[$filed]=$expRes['value'];}else{$valueArray[]=$expRes['value'];}$context['pos']+=$expRes['size'];$totalSize+= $expRes['size'];}else{return ['value'=>False,'size'=>0,'error'=>$expRes['error'],'msg'=>$expRes['msg']];}$expRes = parseFixStr($context,3);if($expRes['error']==0){$filed = 'addr';if($filed){$valueArray[$filed]=$expRes['value'];}else{$valueArray[]=$expRes['value'];}$context['pos']+=$expRes['size'];$totalSize+= $expRes['size'];}else{return ['value'=>False,'size'=>0,'error'=>$expRes['error'],'msg'=>$expRes['msg']];}return ['value'=>$valueArray,'size'=>$totalSize,'error'=>0,'msg'=>'ok'];}登录后复制
运行此测试脚本,得到结果如下:
Array( [value] => Array ( [name] => AB [num] => 1 [age] => 2 [addr] => ABC ) [size] => 13 [error] => 0 [msg] => ok)Array( [value] => Array ( [name] => AB [num] => 1 [addr] => ABC ) [size] => 9 [error] => 0 [msg] => ok)登录后复制
OK ! 第一步的任务已经完成,接下来考虑实现一个带有条件判断的结构体的解析工作。
更多相关问题请访问PHP中文网:https://www.php.cn/
以上就是php 实现类似于pyhon中的Construct库的功能(一) 基本设计思路的详细内容,更多请关注【创想鸟】其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。
发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2160186.html