静态分析-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://xiahunao.cn/news/3032354.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习面试篇

如何理解机器学习数据集的概念 数据集是机器学习的基础&#xff0c;它包括了用于训练和测试模型所需的数据。数据集通常以矩阵的形式存在&#xff0c;其中每一行代表一个样本&#xff08;或实例&#xff09;&#xff0c;每一列代表一个特征&#xff08;或属性&#xff09;。…

SpringAMQP Work Queue 工作队列

消息模型: 代码模拟: 相较于之前的基础队列&#xff0c;该队列新增了消费者 不再是一个&#xff0c;所以我们通过代码模拟出两个consumer消费者。在原来的消费者类里写两个方法 其中消费者1效率高 消费者2效率低 RabbitListener(queues "simple.queue")public voi…

学习网络需要认识的各种设备

网桥&#xff08;bridge&#xff09; 网桥工作在数据链路层&#xff0c;可以把多个局域网连接起来&#xff0c;组成一个更大的局域网 以太网中&#xff0c;数据链路层地址就是mac地址&#xff0c;网桥与集线器的区别就是&#xff0c;网桥会过滤mac&#xff0c;只有目的mac地址…

draw.io 网页版二次开发(2):开始修改代码

目录 一 说明 二 打开开发环境 1. 代码调整 2. 修改访问链接 3. 注意 三 部分功能的代码汇总 1. 保存功能 2. 菜单栏折叠按钮功能 3. 顶部菜单栏 4.在顶部菜单栏中的【文件】菜单中新增选项 &#xff08;1&#xff09; 方法一&#xff1a;单独增加 &#xff08;…

calllback回调函数:同步调用,异步调用,异步回调

纯python代码的异步回调 # _*_ encoding:utf-8 _*_ import time import threading callback_value None onFlag Truedef add(a, b, num):print(f"I am the function: %s, please wait for %d" % (add.__name__, num))time.sleep(num)c a bprint("a b 1 &…

K8S搭建

文章目录 K8S搭建配置要求 安装 Kuboard-Spray加载离线资源包规划并安装集群访问集群重启Kubernetes集群Worker节点不能启动许多Pod一直Crash或不能正常访问 containerd配置网络代理 常用的 kubectl 命令&#xff1a; K8S搭建 安装高可用的Kubernetes集群 配置要求 对于 Kub…

Springboot+mybatis-plus+dynamic-datasource+继承DynamicRoutingDataSource切换数据源

Springbootmybatis-plusdynamic-datasource继承DynamicRoutingDataSource切换数据源 背景 最近公司要求支持saas&#xff0c;实现动态切换库的操作&#xff0c;默认会加载主租户的数据源&#xff0c;其他租户数据源在使用过程中自动创建加入。 解决问题 1.通过请求中设置租…

软件产品检测认证是什么?

软件产品检测认证是软件企业、系统集成商或软件商为了提高自身产品的竞争力&#xff0c;增强客户信心&#xff0c;通过第三方机构对企业的软件产品质量和可靠性进行全面测试与评估的过程。这一过程主要关注软件产品的功能、性能、安全性、可维护性等方面&#xff0c;确保软件产…

FileLink跨网文件传输医疗行业解决方案

随着医疗行业的快速发展&#xff0c;医疗机构之间的信息共享和文件传输需求日益增加。然而&#xff0c;由于网络环境的复杂性和数据安全性的要求&#xff0c;传统的文件传输方式已经无法满足医疗行业的需求。为此&#xff0c;我们推出了FileLink跨网文件传输医疗行业解决方案&a…

炫酷个人主页(源码免费)

炫酷个人主页 效果图部分代码领取源码下期更新预报 效果图 部分代码 <!DOCTYPE html> <!--哪怕是深爱之人 对我们的痛苦一无所知&#xff01;* ░░░░░░░░░░░░░░░░░░░░░░░░▄░░* ░░░░░░░░░▐█░░░░░░░░░░░▄▀▒▌░* ░…

智能座舱语音助手产品方案

一、用户调研与痛点分析 1.目标用户分析 用户画像 性别女性年龄50地域2-3线城市职业退休或退居二线教育中专、 大专、 本科财务家庭财务管理者爱好享受生活、 照顾家庭标签有闲有小钱二、产品定位与卖点提炼 购车目的 愉悦自我&#xff0c; 专属于自己的座驾&#xff1a; 家…

26-ESP32-S3 的 FLASH分区表以及 SPIFFS 文件系统 和spiffsgen.py工具

ESP32-S3 的 SPIFFS 文件系统 ESP32-S3的ROM&#xff0c;RAM&#xff0c;FLASH 存储器类型描述容量内部存储器ROM用于存储固定的程序代码和数据384KBSRAM用于存储运行时的程序数据512KBRTC SRAM在深度睡眠模式下仍然保持数据16KB外部存储器PSRAM片外用于存储运行时的程序数据…

【生信技能树】数据挖掘全流程

R包的安装&#xff0c;每次做分析的时候先运行这段代码把R包都安装好了&#xff0c;这段代码不需要任何改动&#xff0c;每次分析直接运行。 options("repos""https://mirrors.ustc.edu.cn/CRAN/") if(!require("BiocManager")) install.packag…

vue 点击平滑到指定位置并绑定页面滑动效果

1.html元素 写出对应的数据块&#xff08;注意添加ref) 用于获取元素位置 <template><div class"index-page" ><div class"top-head" ref"index"><img src"logo.png" style"height: 40px;margin-right: 2…

大模型面试常考知识点1

文章目录 1. 写出Multi-Head Attention2. Pre-Norm vs Post-Norm3. Layer NormRMS NormBatch Norm 4. SwiGLU从ReLU到SwishSwiGLU 5. AdamW6. 位置编码Transformer位置编码RoPEALibi 7. LoRA初始化 参考文献 1. 写出Multi-Head Attention import torch import torch.nn as nn …

【VMware】vSphere 8.0 安装和设置简介

本信息的目标读者为熟悉虚拟机技术和数据中心操作并具有丰富经验的 Windows 或 Linux 系统管理员。 vSphere 8.0 提供了各种安装和设置选项&#xff0c;这些选项定义了相应的任务序列。 vSphere 的两个核心组件是 ESXi 和 vCenter Server。ESXi 是可用于创建和运行虚拟机和虚拟…

【算法】最短路问题 bfs 到 dijkstra

1976、到达目的地的方案数 你在一个城市里&#xff0c;城市由 n 个路口组成&#xff0c;路口编号为 0 到 n - 1 &#xff0c;某些路口之间有 双向 道路。输入保证你可以从任意路口出发到达其他任意路口&#xff0c;且任意两个路口之间最多有一条路。 给你一个整数 n 和二维整…

laravel8 导入 excel常见问题

上传xls 或 xlsx 文件后&#xff0c;文件解析为 zip 格式&#xff0c;输入正常情况&#xff0c;不影响解析 里面的内容 遇到解析内容&#xff0c;解析为空的情况&#xff0c;可能是 因为excel 存在多个 Sheet1 造成&#xff0c;服务器不能解析一个 Sheet1 的情况&#xff0…

小程序获取手机号,用户昵称,头像

一、手机号 在微信小程序中&#xff0c;获取用户手机号也需要用户的明确授权。你可以使用 button 组件的 open-type 属性设置为 getPhoneNumber 来实现这个功能。当用户点击这个按钮时&#xff0c;会弹出一个对话框请求用户的授权。如果用户同意&#xff0c;你可以在 bindgetp…

如何通过优质服务建立客户忠诚度,促进口碑传播

在生活中&#xff0c;我们经常听到“客户忠诚度”一词&#xff0c;但很少有人真正理解客户忠诚度的含义。其实&#xff0c;客户忠诚度是指企业忠实于其所提供的产品或服务的程度&#xff0c;客户忠诚度对企业和个人都非常重要。高忠诚度的客户会给企业带来巨大的经济和社会效益…