静态分析-RIPS-源码解析记录-01

token流扫描重构部分,这一部分主要利用php的token解析api解析出来的token流,对其中的特定token进行删除、替换、对于特定的语法结构进行重构,保持php语法结构上的一致性

解析主要在lib/scanner.php中通过Tokenizer这个类来实现,也就是在main.php中开始调用new scanner的对象,准备开始扫描漏洞,此时在scanner的构造函数中完成token流的解析

这里主要在scanner这个类的构造函数里面完整待扫描的文件的token解析,主要由以下四个步骤完成

接着到tokenize中进行具体的token解析,这里要借助php zend引擎自带的一个词法分析的函数,token_get_all();

 

 对于上面这个文件,解析以后如下图所示:

array(11) {[0] =>array(3) {[0] =>int(379)[1] =>string(6) "<?php
"[2] =>int(1)}[1] =>array(3) {[0] =>int(320)[1] =>string(2) "$a"[2] =>int(2)}[2] =>string(1) "="[3] =>string(1) "["[4] =>array(3) {[0] =>int(323)[1] =>string(3) ""a""[2] =>int(2)}[5] =>string(1) ","[6] =>array(3) {[0] =>int(323)[1] =>string(3) ""b""[2] =>int(2)}[7] =>string(1) "]"[8] =>string(1) ";"[9] =>array(3) {[0] =>int(382)[1] =>string(1) "
"[2] =>int(2)}[10] =>array(3) {[0] =>int(381)[1] =>string(3) "?>
"[2] =>int(3)}
}

解析出来的每个token以数组形式存在,默认一个token数组包括三个元素,0代表token常量,1代表token值,2代表行号,此时得到的是最初的token组,接下来要经过一系列的token处理,以为php中实际上变量表示方法多样,要做到统一才方便后面分析。

第一部分: 

prepare_tokens:

 function prepare_tokens(){    // delete whitespaces and other unimportant tokens, rewrite some special tokensfor($i=0, $max=count($this->tokens); $i<$max; $i++) //遍历token数组{if( is_array($this->tokens[$i]) )  //a. unset掉可忽略token,比如php的开始标签<?php以及一些空格,比如if   ()变为if(),方便后面进行条件语句的解析,以及像if(): xxx endif 中间的文本unset掉b. 闭合标签变分号;  c.<?= 标签表echo{if( in_array($this->tokens[$i][0], Tokens::$T_IGNORE) )unset($this->tokens[$i]);else if( $this->tokens[$i][0] === T_CLOSE_TAG )$this->tokens[$i] = ';';    else if( $this->tokens[$i][0] === T_OPEN_TAG_WITH_ECHO )$this->tokens[$i][1] = 'echo';} // @ (depress errors) disturbs connected token handlingelse if($this->tokens[$i] === '@')  //unset掉@符号{unset($this->tokens[$i]);}    // rewrite $array{index} to $array[index]  //对于数组处理如果当前是花括号并且前一个token是变量else if( $this->tokens[$i] === '{'&& isset($this->tokens[$i-1]) && ((is_array($this->tokens[$i-1]) && $this->tokens[$i-1][0] === T_VARIABLE)|| $this->tokens[$i-1] === ']') ) //或者上一个token是[,当前是{,则肯定是数组变量(主要是多维数组){$this->tokens[$i] = '['; //则令当前token为左方括号$f=1;while($this->tokens[$i+$f] !== '}') //此时while循环找下一个与当前花括号对应的右花括号{$f++;if(!isset($this->tokens[$i+$f])){addError('Could not find closing brace of '.$this->tokens[$i-1][1].'{}.', array_slice($this->tokens, $i-1, 2), $this->tokens[$i-1][2], $this->filename);break;    //没找到则退出,说明语法有问题}}$this->tokens[$i+$f] = ']';  //否则令右花括号为]}    }// rearranged key index of tokens$this->tokens = array_values($this->tokens);}    

第二部分:

接着是对多维数组的重构:

// rewrite $arrays[] to $variables and save keys in $tokens[$i][3]

从代码注释可以看出这个函数将多维数组的所有键名保存在当前解析为变量token的数组第四个元素中

function array_reconstruct_tokens(){    for($i=0,$max=count($this->tokens); $i<$max; $i++) //遍历所有token数组{// check for arraysif( is_array($this->tokens[$i]) && $this->tokens[$i][0] === T_VARIABLE && $this->tokens[$i+1] === '[' ) //当前token是个变量,并且下一个token是[,则最少即为一维数组{    $this->tokens[$i][3] = array(); //初始化第四个元素为数组$has_more_keys = true;  $index = -1;$c=2;// loop until no more index found: array[1][2][3]while($has_more_keys && $index < MAX_ARRAY_KEYS) //while循环遍历多维数组,max默认为10(这个数已经够了){$index++;// save constant index as constant   //找到当前变量对应的右括号,主要是针对常量if(($this->tokens[$i+$c][0] === T_CONSTANT_ENCAPSED_STRING || $this->tokens[$i+$c][0] === T_LNUMBER || $this->tokens[$i+$c][0] === T_NUM_STRING || $this->tokens[$i+$c][0] === T_STRING) && $this->tokens[$i+$c+1] === ']'){         unset($this->tokens[$i+$c-1]); //unset掉左括号$this->tokens[$i][3][$index] = str_replace(array('"', "'"), '', $this->tokens[$i+$c][1]); //把键名放到第四个数组元素中unset($this->tokens[$i+$c]); //unset掉键名unset($this->tokens[$i+$c+1]); //unset掉右括号$c+=2; #c+2尝试找到下一维数组// save tokens of non-constant index as token-array for backtrace later  //$a[$b][][]对于这种非常量索引的情况  } else{$this->tokens[$i][3][$index] = array(); $newbraceopen = 1; //就当作是左括号的个数unset($this->tokens[$i+$c-1]); //unset掉左括号while($newbraceopen !== 0) {    if( $this->tokens[$i+$c] === '[' ){$newbraceopen++;  //哇,又遇到新的一个左括号}else if( $this->tokens[$i+$c] === ']' ) {$newbraceopen--; //此时说明一组左右括号遍历完}else{$this->tokens[$i][3][$index][] = $this->tokens[$i+$c]; //此时将变量索引对应的数组保存在第四个元素中 }    unset($this->tokens[$i+$c]); //unset掉该变量索引或unset掉右括号或左括号$c++; //就把它当作游标吧,游标不断滑动if(!isset($this->tokens[$i+$c])) //尝试找到=或者分号,实际就是结束当前数组的符号,没有找到则break退出{addError('Could not find closing bracket of '.$this->tokens[$i][1].'[].', array_slice($this->tokens, $i, 5), $this->tokens[$i][2], $this->filename);break;    }}unset($this->tokens[$i+$c-1]); //这一处unset为了处理特殊情况}if($this->tokens[$i+$c] !== '[')$has_more_keys = false;$c++;    }    $i+=$c-1;}}// return tokens with rearranged key index$this->tokens = array_values($this->tokens);        }

 比如对于下面这种索引为变量的数组:

 解析以后将所有维度的键名存储在$a这个token的第四个数组元素中

 

 这个算法设计的还是挺巧妙的,每一处unset都设计的很恰当

 

 这个unset就刚刚假设当前游标为数组索引或右括号,-1直接unset掉左括号

 

 ①处当遍历数组变量完成后则置为true,此时因为游标在=或者;处,因此c++完后$i应该+c-1回到=或者;处

对于$a[$b][][]处理完后就是:

 

第三部分:

fix_tokens:

这一部分主要是重构一些token信息

function fix_tokens(){    for($i=0; $i<($max=count($this->tokens)); $i++){// convert `backticks` to backticks()  #处理反引号if( $this->tokens[$i] === '`' ){        $f=1;while( $this->tokens[$i+$f] !== '`' )  #通过while循环来,将`xxx` 转换成backticks标识的token{    // get line_nr of any near tokenif( is_array($this->tokens[$i+$f]) )$line_nr = $this->tokens[$i+$f][2];  #此时反引号中间内容的行号$f++; #f++走到右反引号if(!isset($this->tokens[$i+$f]) || $this->tokens[$i+$f] === ';')  #无闭合则报错{addError('Could not find closing backtick `.', array_slice($this->tokens, $i, 5), $this->tokens[$i+1][2], $this->filename);break;    }}if(!empty($line_nr)) #若反引号中间内容不为空,则进行重构{ $this->tokens[$i+$f] = ')'; #将右引号变为圆括号)$this->tokens[$i] = array(T_STRING, 'backticks', $line_nr);  #将左反引号声明一个backticks的token// add element backticks() to array             $this->tokens = array_merge(   #对当前token进行重构array_slice($this->tokens, 0, $i+1), array('('),  #因为刚才将左反引号替换了,所以此时需要再添加一个左圆括号array_slice($this->tokens, $i+1) #拼接上后面从右圆括号开始的token,所以就是`xxx`  变为 backtricks(xxx));    }}#接下来要对一些条件语句、循环语句进行解析,主要为IF,else if,for,foreach,while// real tokenelse if( is_array($this->tokens[$i]) )  {    // rebuild if-clauses, for(), foreach(), while() without { } #首先重构没有花括号的,即只有方法体只有单条语句if ( ($this->tokens[$i][0] === T_IF || $this->tokens[$i][0] === T_ELSEIF || $this->tokens[$i][0] === T_FOR || $this->tokens[$i][0] === T_FOREACH || $this->tokens[$i][0] === T_WHILE) && $this->tokens[$i+1] === '(' ){        // skip condition in ( ) #这个while主要是跳过条件判断(),继续扫描后面的token,对token数组并不做处理$f=2;$braceopen = 1;while($braceopen !== 0 ) {if($this->tokens[$i+$f] === '(')$braceopen++;else if($this->tokens[$i+$f] === ')')$braceopen--;$f++;if(!isset($this->tokens[$i+$f])){addError('Could not find closing parenthesis of '.$this->tokens[$i][1].'-statement.', array_slice($this->tokens, $i, 5), $this->tokens[$i][2], $this->filename);break;    }}    // alternate syntax while(): endwhile; #这个if条件主要是为了给php的替代语法结构加上左右花括号,对于每一种条件或者循环关键字,都对应了相应的结束标记,因此结合一个c变量,通过其自增来找到相应关键字的闭合token,然后再将:和endif都放到花括号内if($this->tokens[$i+$f] === ':'){switch($this->tokens[$i][0]){case T_IF:case T_ELSEIF: $endtoken = T_ENDIF; break;case T_FOR: $endtoken = T_ENDFOR; break;case T_FOREACH: $endtoken = T_ENDFOREACH; break;case T_WHILE: $endtoken = T_ENDWHILE; break;default: $endtoken = ';';}$c=1;while( $this->tokens[$i+$f+$c][0] !== $endtoken){$c++;if(!isset($this->tokens[$i+$f+$c])){addError('Could not find end'.$this->tokens[$i][1].'; of alternate '.$this->tokens[$i][1].'-statement.', array_slice($this->tokens, $i, $f+1), $this->tokens[$i][2], $this->filename);break;    }}$this->wrapbraces($i+$f+1, $c+1, $i+$f+$c+2);}#这个if条件主要是为了针对 if() echo "1";只有一条指令,将其放到花括号内// if body not in { (and not a do ... while();) wrap next instruction in braceselse if($this->tokens[$i+$f] !== '{' && $this->tokens[$i+$f] !== ';'){$c=1;while($this->tokens[$i+$f+$c] !== ';' && $c<$max){$c++;}$this->wrapbraces($i+$f, $c+1, $i+$f+$c+1);}} #为else if 添加花括号// rebuild else without { }    else if( $this->tokens[$i][0] === T_ELSE && $this->tokens[$i+1][0] !== T_IF&& $this->tokens[$i+1] !== '{'){    $f=2;while( $this->tokens[$i+$f] !== ';' && $f<$max){        $f++;}$this->wrapbraces($i+1, $f, $i+$f+1);}// rebuild switch (): endswitch;   #switch语句的处理,和if while等差不多,先扫描跳过判断语句     else if( $this->tokens[$i][0] === T_SWITCH && $this->tokens[$i+1] === '('){$newbraceopen = 1;$c=2;while( $newbraceopen !== 0 ){// watch function calls in function callif( $this->tokens[$i + $c] === '(' ){$newbraceopen++;}else if( $this->tokens[$i + $c] === ')' ){$newbraceopen--;}                    else if(!isset($this->tokens[$i+$c]) || $this->tokens[$i + $c] === ';'){addError('Could not find closing parenthesis of switch-statement.', array_slice($this->tokens, $i, 10), $this->tokens[$i][2], $this->filename);break;    }$c++;}#此时达到switch的方法体,因为switch一般来说是带花括号的,但对于endswitch的情况需要特殊处理一下,变为花括号形式// switch(): ... endswitch;if($this->tokens[$i + $c] === ':'){$f=1;while( $this->tokens[$i+$c+$f][0] !== T_ENDSWITCH) #这里是通过f来找endswitch,c找:{$f++;if(!isset($this->tokens[$i+$c+$f])){addError('Could not find endswitch; of alternate switch-statement.', array_slice($this->tokens, $i, $c+1), $this->tokens[$i][2], $this->filename);break;    }}$this->wrapbraces($i+$c+1, $f+1, $i+$c+$f+2);}}// rebuild switch case: without { }    #switch处理完了,此时处理switch内部的case这一部分主要是将每一条case后面的全部补全花括号else if( $this->tokens[$i][0] === T_CASE ){$e=1;while($this->tokens[$i+$e] !== ':' && $this->tokens[$i+$e] !== ';') #找到分号{$e++;if(!isset($this->tokens[$i+$e])){addError('Could not find : or ; after '.$this->tokens[$i][1].'-statement.', array_slice($this->tokens, $i, 5), $this->tokens[$i][2], $this->filename);break;    }}$f=$e+1;if(($this->tokens[$i+$e] === ':' || $this->tokens[$i+$e] === ';')&& $this->tokens[$i+$f] !== '{' && $this->tokens[$i+$f][0] !== T_CASE && $this->tokens[$i+$f][0] !== T_DEFAULT){$newbraceopen = 0;while($newbraceopen || (isset($this->tokens[$i+$f]) && $this->tokens[$i+$f] !== '}' && !(is_array($this->tokens[$i+$f]) && ($this->tokens[$i+$f][0] === T_BREAK || $this->tokens[$i+$f][0] === T_CASE || $this->tokens[$i+$f][0] === T_DEFAULT || $this->tokens[$i+$f][0] === T_ENDSWITCH) ) )){        if($this->tokens[$i+$f] === '{')$newbraceopen++;else if($this->tokens[$i+$f] === '}')    $newbraceopen--;$f++;if(!isset($this->tokens[$i+$f])){addError('Could not find ending of '.$this->tokens[$i][1].'-statement.', array_slice($this->tokens, $i, $e+5), $this->tokens[$i][2], $this->filename);break;    }}if($this->tokens[$i+$f][0] === T_BREAK){if($this->tokens[$i+$f+1] === ';')$this->wrapbraces($i+$e+1, $f-$e+1, $i+$f+2);// break 3;    else$this->wrapbraces($i+$e+1, $f-$e+2, $i+$f+3);}    else{   # 无break的情况$this->wrapbraces($i+$e+1, $f-$e-1, $i+$f);}    $i++;}}// rebuild switch default: without { }  #针对default的情况,如果没有花括号,则添加花括号  else if( $this->tokens[$i][0] === T_DEFAULT&& $this->tokens[$i+2] !== '{' ){$f=2;$newbraceopen = 0;while( $this->tokens[$i+$f] !== ';' && $this->tokens[$i+$f] !== '}' || $newbraceopen ){        if($this->tokens[$i+$f] === '{')$newbraceopen++;else if($this->tokens[$i+$f] === '}')    $newbraceopen--;$f++;if(!isset($this->tokens[$i+$f])){addError('Could not find ending of '.$this->tokens[$i][1].'-statement.', array_slice($this->tokens, $i, 5), $this->tokens[$i][2], $this->filename);break;    }}$this->wrapbraces($i+2, $f-1, $i+$f+1);}#函数名小写// lowercase all function names because PHP doesn't care    else if( $this->tokens[$i][0] === T_FUNCTION ){$this->tokens[$i+1][1] = strtolower($this->tokens[$i+1][1]);} #函数调用小写   else if( $this->tokens[$i][0] === T_STRING && $this->tokens[$i+1] === '('){$this->tokens[$i][1] = strtolower($this->tokens[$i][1]);}    // switch a do while with a while (the difference in loop rounds doesnt matter// and we need the condition to be parsed before the loop tokens)else if( $this->tokens[$i][0] === T_DO ){$f=2;$otherDOs = 0;// f = T_WHILE token position relative to i#此时去找到对应该DO的while的tokenwhile( $this->tokens[$i+$f][0] !== T_WHILE || $otherDOs ){        #忽略内层的DO while体if($this->tokens[$i+$f][0] === T_DO)$otherDOs++;else if($this->tokens[$i+$f][0] === T_WHILE)$otherDOs--;$f++; #用f来标志找到的while位置if(!isset($this->tokens[$i+$f])){addError('Could not find WHILE of DO-WHILE-statement.', array_slice($this->tokens, $i, 5), $this->tokens[$i][2], $this->filename);break;    }}// rebuild do while without {} (should never happen but we want to be sure)#对于do后不带花括号的情况,带上花括号if($this->tokens[$i+1] !== '{'){$this->wrapbraces($i+1, $f-1, $i+$f);// by adding braces we added two new tokens$f+=2; #因为在最外层的while前加了两个花括号占位,因此f+2才代表while的位置}#d代表while后的分号位置,这样不改变f所指的位置方便后面替换结构$d=1;// d = END of T_WHILE condition relative to iwhile( $this->tokens[$i+$f+$d] !== ';' && $d<$max ){$d++;}#对do while语句进行重构,变成while结构// reorder tokens and replace DO WHILE with WHILE$this->tokens = array_merge(array_slice($this->tokens, 0, $i), // before DO  array_slice($this->tokens, $i+$f, $d), // WHILE condition f指向while d指向while结束array_slice($this->tokens, $i+1, $f-1), // DO WHILE loop tokens i指向do循环体,f-1即内容结束array_slice($this->tokens, $i+$f+$d+1, count($this->tokens)) // rest of tokens without while condition  while之后的token数组);    }}    }// return tokens with rearranged key index$this->tokens = array_values($this->tokens);}

上面函数名小写要注意一点,php是弱类型语言,这里其本身不支持函数重载,即没有java的类的多态特性,但是同为解释型语言的python是支持函数重载的

第四部分:

token解析的最后一部分为:

function fix_ternary(){for($i=0,$max=count($this->tokens); $i<$max; $i++){if( $this->tokens[$i] === '?' ){unset($this->tokens[$i]);// condition in brackets: fine, delete conditionif($this->tokens[$i-1] === ')'){   #先找到)右括号,然后减f一直找到左括号,一直unset到左括号(unset($this->tokens[$i-1]);// delete tokens till ( $newbraceopen = 1;$f = 2;while( $newbraceopen !== 0 && $this->tokens[$i - $f] !== ';'){if( $this->tokens[$i - $f] === '(' ){$newbraceopen--;}else if( $this->tokens[$i - $f] === ')' ){$newbraceopen++;}unset($this->tokens[$i - $f]);    $f++;if(($i-$f)<0){addError('Could not find opening parenthesis in ternary operator (1).', array_slice($this->tokens, $i-5, 10), $this->tokens[$i+1][2], $this->filename);break;    }}#判断左括号左边是否是!或是自定义函数调用或者是isset、empty函数调用//delete token before, if T_STRINGif($this->tokens[$i-$f] === '!' || (is_array($this->tokens[$i-$f]) && ($this->tokens[$i-$f][0] === T_STRING || $this->tokens[$i-$f][0] === T_EMPTY || $this->tokens[$i-$f][0] === T_ISSET))){unset($this->tokens[$i-$f]);}}// condition is a check or assignment#判断问号之前是不是逻辑比较,是的话肯定有操作数,需要unset掉else if(in_array($this->tokens[$i-2][0], Tokens::$T_ASSIGNMENT) || in_array($this->tokens[$i-2][0], Tokens::$T_OPERATOR) ){// remove both operandsunset($this->tokens[$i-1]); #右操作数删除unset($this->tokens[$i-2]); #删除运算符// if operand is in bracesif($this->tokens[$i-3] === ')')  #判断左边是否是函数调用,跟上面unset过程差不多,理想情况下是a()==1,但是对于1==a()没有考虑进去,因此对于这种unset并不能完全消除token,就直接走上面第一种a()这种形式的token解析{// delete tokens till ( $newbraceopen = 1;$f = 4;while( $newbraceopen !== 0 ){if( $this->tokens[$i - $f] === '(' ){$newbraceopen--;}else if( $this->tokens[$i - $f] === ')' ){$newbraceopen++;}unset($this->tokens[$i - $f]);    $f++;if(($i-$f)<0 || $this->tokens[$i - $f] === ';'){addError('Could not find opening parenthesis in ternary operator (2).', array_slice($this->tokens, $i-8, 6), $this->tokens[$i+1][2], $this->filename);break;    }}#删除函数调用//delete token before, if T_STRINGif(is_array($this->tokens[$i-$f]) && ($this->tokens[$i-$f][0] === T_STRING || $this->tokens[$i-$f][0] === T_EMPTY || $this->tokens[$i-$f][0] === T_ISSET)){unset($this->tokens[$i-$f]);}}unset($this->tokens[$i-3]);}// condition is a single variable, delete#对于单变量  $a? unset掉else if(is_array($this->tokens[$i-1]) && $this->tokens[$i-1][0] === T_VARIABLE){unset($this->tokens[$i-1]);}}    }// return tokens with rearranged key index$this->tokens = array_values($this->toknes);    }

这一部分主要就是对于三元操作符删除掉?前面的判断条件,此时只保留?后面的两种取值情况

参考:

https://xz.aliyun.com/t/2605#toc-6  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/7841.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux或ubuntu环境下需要自行安装vivado USB Program下载程序驱动

如果在linux或ubuntu环境下&#xff0c;不安装驱动是无法下载FPGA程序的。在linux或ubuntu环境下安装程序不要自动安装。 johnjohn-wang:~/vitis2021.2/Vivado/2021.2/data/xicom/cable_drivers/lin64/install_script/install_drivers$ sudo ./install_drivers

【C++】从零开始认识多态

送给大家一句话&#xff1a; 一个犹豫不决的灵魂&#xff0c;奋起抗击无穷的忧患&#xff0c;而内心又矛盾重重&#xff0c;真实生活就是如此。 ​​​​ – 詹姆斯・乔伊斯 《尤利西斯》 _φ(*&#xffe3;ω&#xffe3;)&#xff89;_φ(*&#xffe3;ω&#xffe3;)&…

Elsevier旗下双1区TOP刊,8.8分影响因子加上超低自引率,各指标领跑计算机类SCI

【SciencePub学术】 今天小编给大家带来了一本计算机类的高分优刊解读&#xff0c;隶属于Elsevier出版社&#xff0c;JCR1区&#xff0c;中科院1区TOP&#xff0c;影响因子高达8.7&#xff0c;领域相符的学者可考虑&#xff01; APPLIED SOFT COMPUTING 1 期刊概况 【期刊简…

3D渲染是什么?渲染100邀请码1a12

3D渲染是把3D模型转换为2D图像或动画的过程&#xff0c;涉及到多方面知识&#xff0c;这篇文章我们就来了解下。 1、3D渲染的原理 3D渲染的原理是模拟光线在三维空间中的传播和反射&#xff0c;根据物体在空间中的分布&#xff0c;计算出每个像素的颜色和亮度等数值&#xff…

⚡REST 和 SOAP 协议有什么区别?

原文链接&#xff1a;https://document360.com/blog/rest-vs-soap/ API 是应用程序编程接口&#xff08;Application Programming Interface&#xff09;的缩写。API 规定了不同的软件组件应如何以编程方式进行交互和通信。 最常见的 API 类型就是 Web API。网络应用&#xff…

笔试强训Day19 数学知识 动态规划 模拟

[编程题]小易的升级之路 题目链接&#xff1a;小易的升级之路__牛客网 思路&#xff1a; 按题目写即可 注意辗转相除法。 AC code&#xff1a; #include<iostream> using namespace std; int gcd(int a, int b) {return b ? gcd(b, a % b) : a; } int main() {int n…

HCIP的学习(13)

第五章&#xff0c;重发布和路由策略 重发布 ​ 在路由协议的边界设备上&#xff0c;将某一种路由协议的路由信息引入到另一种路由协议中&#xff0c;这个操作被称为路由引入或者路由重分发。----技术本质为重发布。 条件 必须存在ASBR设备&#xff08;路由边界设备&#x…

算法提高之树的最长路径

算法提高之树的最长路径 核心思想&#xff1a;树形dp 枚举路径的中间节点用f1[i] 表示i的子树到i的最长距离,f2[i]表示次长距离最终答案就是max(f1[i]f2[i]) #include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N …

matlab

图像配准&#xff1a; %手动选择执行图片(由于程序为分开&#xff0c;此处保存的mat文件为图MRI6的信息&#xff0c;所以请选择图MRI6) [filename,pathname]uigetfile({*.jpg;*.bmp;*.tif;*.png;*.gif,All Image Files;*.*,All Files}); image imread([pathname,filename]); …

本机MySQL数据库服务启动了,但是cmd登录不上10061

注意&#xff1a;不建议安装MySQL8&#xff0c;建议直接使用phpstudy中自带的MySQL5.7 错误信息 ERROR 2003 (HY000): Cant connect to MySQL server on x.x.x.x (10061) 原因 可能是端口号错误。比如修改了my.ini中&#xff0c;或者phpstudy中数据库端口的配置&#xff0c;…

【Linux】Linux线程

一、Linux线程的概念 1.什么是线程 1.一个进程的一个执行线路叫做线程&#xff0c;线程的一个进程内部的控制序列。 2.一个进程至少有一个执行线程 3.线程在进程内部&#xff0c;本质是在进程地址空间内运行 4.操作系统将进程虚拟地址空间的资源分配给每个执行流&#xff0…

【C 数据结构-动态内存管理】3. 伙伴系统管理动态内存

文章目录 【 1. 伙伴系统的结构设计 】【 2. 分配算法 】【 3. 回收算法 】 伙伴系统 本身是一种动态管理内存的方法&#xff0c;和边界标识法的区别是&#xff1a;使用伙伴系统管理的存储空间&#xff0c;无论是空闲块还是占用块&#xff0c;大小都是 2 的 n 次幂&#xff08;…

新火种AI|挑战谷歌,OpenAI要推出搜索引擎?

作者&#xff1a;一号 编辑&#xff1a;美美 在AI革新的浪潮下&#xff0c;谷歌搜索迎来了越来越多的“挑战者”。 最近&#xff0c;据多家外媒的消息&#xff0c;有知情人士透露&#xff0c;OpenAI正计划上线一款基于ChatGPT的大型产品&#xff0c;将提供一个新的搜索引擎&…

[译]Elasticsearch _source Doc_values And Store Performance

原文地址 https://sease.io/2021/02/field-retrieval-performance-in-elasticsearch.html 在这篇博文中&#xff0c;我想从性能的角度探讨 Elasticsearch 为我们存储字段和查询时检索字段提供了哪些可能性。 事实上&#xff0c;Lucene&#xff08;Elasticsearch 和 Solr 构建的…

【WEB前端2024】开源智体世界:乔布斯3D纪念馆-第16课-跳转私信

【WEB前端2024】开源智体世界&#xff1a;乔布斯3D纪念馆-第16课-跳转私信 使用dtns.network德塔世界&#xff08;开源的智体世界引擎&#xff09;&#xff0c;策划和设计《乔布斯超大型的开源3D纪念馆》的系列教程。dtns.network是一款主要由JavaScript编写的智体世界引擎&am…

Map集合的实现类~HashMap

存储结构&#xff1a;哈希表 键重复依据是hashCode和equals方法&#xff08;键不能重复&#xff09; 添加&#xff1a; 先创建Student类&#xff0c;那么往HashSet添加的就是Student对象作为键值&#xff0c;后面的作为值 删除&#xff1a; 判断&#xff1a; 遍历&#xff1a…

就业班 第三阶段(redis) 2401--5.7 day2 redis2 哨兵(前提是做好了主从)+redis集群

1、设置密码&#xff08;redis&#xff09; 先在redis.conf里面找到这个 后面写上要设置的密码即可 2、哨兵模式 监控redis集群中master状态的的工具 在做了主从的前提下 主 从1 从2 作用 1)&#xff1a;Master状态检测 2)&#xff1a;如果Master异常&#xff0c;则会进行…

(四)JSP教程——request内置对象

request对象是将客户端浏览器数据提交给服务器端JSP页面的唯一数据通道&#xff0c;通过该通道JSP页面能够获取浏览器信息、form表单信息、URL参数信息等。 1.from表单向JSP文件传递数据 form表单是浏览器向服务器传递数据的一种基本机制&#xff0c;包含两种方式&#xff1a;…

C/C++开发环境配置

配置C/C开发环境 1.下载和配置MinGW-w64 编译器套件 下载地址&#xff1a;https://sourceforge.net/projects/mingw-w64/files/mingw-w64/mingw-w64-release/ 下载后解压并放至你容易管理的路径下&#xff08;我是将其放在了D盘的一个software的文件中管理&#xff09; 2.…

快速找出存(不存在)在某个(或多个)文件的文件夹

首先&#xff0c;需要用到的这个工具&#xff1a; 度娘网盘 提取码&#xff1a;qwu2 蓝奏云 提取码&#xff1a;2r1z 想要找出有下面这个文件存在的文件夹 切换到批量文件复制版块&#xff0c;快捷键Ctrl5 右侧&#xff0c;搜索添加 选定范围&#xff0c;勾选搜索文件夹、包…