php 简析对象,PHP白盒审计工具RIPS源码简析

RIPS是一款对PHP源码进行风险扫描的工具,其对代码扫描的方式是常规的正则匹配,确定sink点;还是如flowdroid构建全局数据流图,并分析存储全局数据可达路径;下面就从其源码上略探一二。

1、扫描流程

分析其源码前,我们需要缕清其扫描的流程,方便后面的分析,下图展示其进行扫描的主界面:

91fa37c87851bde4be60d5330c1b566e.png

先简单介绍下每个标签的基本功能:path/file:待扫描代码的文件地址;

subdirs:是否对代码的子目录进行扫描,勾选将会扫描子目录,不勾选只扫描当前目录下的PHP文件;

verbosity level:选择source点,即可控制的输入点,定义在rips下config/sources.php中;

vuln type:选择sink点,即可能会触发各种风险的函数,定义在rips下config/sinks.php中;

scan:选择好前面的选项,点击该按钮即可开始扫描;

code style:扫描结果的展示方式;

/regex/:要搜索内容的正则表达式;

search:根据正则表达式对全局代码进行搜索;

在进行扫描时一般将扫描文件目录粘贴到第一栏中,点击scan进行扫描,那么这个scan就是执行扫描的开始点;点击scan按钮会调用js/script.js中的scan方法进行扫描,该方法将会获取在主界面中获取的参数,并通过XMLHttpRequest方法传递给rips主目录下的main.php中进行处理。在main.php中主要执行一些赋值的操作,及调用scanner.php进行具体的扫描,下面的代码便是其调用scanner.php的相关代码。// scan

$scan = new Scanner($file_scanning, $scan_functions, $info_functions, $source_functions);

$scan->parse();

$scanned_files[$file_scanning] = $scan->inc_map;

其赋值对象主要是$file_scanning, $scan_functions, $info_functions, $source_functions这四个对象,四个对象的含义如下所示:$file_scanning:表示要扫描的php文件,如果扫描的对象是一个文件,那么该参数就代表这个对象本身;如果扫描对象是一个目录,RIPS将会对目录中的文件进行逐个扫描,该对象就代表目录中的每个文件。

$scan_functions:sink点,会触发漏洞的函数名称的列表,根据选择的vuln type,通过config/sinks.php进行构造。

$info_functions:设备信息,根据扫描文件中使用的函数特征值确定,通过config/info.php进行构造。

$source_functions:source点,可控制的输入点,通过config/sources.php进行构造。

scanner的扫描可以把它大致分为两步,第一步是初始化Scanner对象;第二步则是最关键的漏洞扫描,通过parse()方法进行。

2、代码扫描

2.1 初始化Scanner对象

此处主要通过__construct方法执行一些初始化操作,对其中一些关键代码进行说明:function __construct($file_name, $scan_functions, $info_functions, $source_functions)

{

$this->file_name = $file_name;

$this->scan_functions = $scan_functions;

$this->info_functions = $info_functions;

$this->source_functions = $source_functions;

此处主要是将main.php中传递过来的文件赋值给类变量,这几个变量是初始化后面一些类变量的基础。下面将是初始化的关键步骤,为方便说明将在代码中直接进行注释说明:$this->inc_file_stack = array(realpath($this->file_name));                           // 待扫描文件的真实地址,存入数组中

$this->inc_map = array();

$this->include_paths = Analyzer::get_ini_paths(ini_get("include_path"));   // 文件所包含的路径,单个结果一般为:Array( [0] => .  [1] => ),即文件的自身路径

$this->file_pointer = end($this->inc_file_stack);                                          // 文件地址数组中最后的元素,值为文件自身真实路径

if(!isset($GLOBALS['file_sinks_count'][$this->file_pointer]))

$GLOBALS['file_sinks_count'][$this->file_pointer] = 0;                              // 初始化该文件sink点统计数目

$this->lines_stack = array();

$this->lines_stack[] = file($this->file_name);                                             // 读取待扫描文件内容,存储到一个数组中

$this->lines_pointer = end($this->lines_stack);                                        // 由于文件内容存储在数组的第一个元素中,且数组长度为1,此处代表将文件内容逐行存储在一个数组中

$this->tif = 0; // tokennr in file

$this->tif_stack = array();

// preload output

echo $GLOBALS['fit'] . '|' . $GLOBALS['file_amount'] . '|' . $this->file_pointer . ' (tokenizing)|' . $GLOBALS['timeleft'] . '|' . "\n";

@ob_flush();

flush();

// tokenizing

$tokenizer = new Tokenizer($this->file_pointer);

$this->tokens = $tokenizer->tokenize(implode('',$this->lines_pointer));

unset($tokenizer);                                                                                     // 上面几行是整个分析的关键,将在下面进行详细的说明

// add auto includes from php.ini

if(ini_get('auto_prepend_file')) { $this->add_auto_include(ini_get('auto_prepend_file'), true);}

if(ini_get('auto_append_file')) { $this->add_auto_include(ini_get('auto_append_file'), false);}

// 校验php配置文件(php.ini)中是否存在自动包含的文件,如果存在将直接添加到$this->tokens的类变量中

此处将粗略说明$this->tokens类变量的生成,该变量的生成主要调用lib/tokenizer.php中的方法,下面是其关键代码:public function tokenize($code) {

$this->tokens = token_get_all($code);

$this->prepare_tokens();

$this->array_reconstruct_tokens();

$this->fix_tokens();

$this->fix_ternary();

#die(print_r($this->tokens));

return $this->tokens;

}

通过调用ZEND引擎的token_get_all方法将PHP源码分解成PHP tokens(参考:http://php.net/manual/en/function.token-get-all.php),并对这些tokens进行相关处理优化,处理优化的过程没有进行仔细的研究,此处不做详细介绍。为了让大家对ZEND引擎生成的tokens有个更直观的认识,这是将使用一个简单的例子分别展示源码、token_get_all生成的原始tokens、处理后的tokens,通过后面的对比可以粗略的看出,处理后的tokens比原始生成的tokens更加简洁,去除了一些对于风险扫描无用的tokens,如<?php 、?>、空字节等。如下所示:

源码:<?php  echo $_GET('info'); ?>

原始tokens:Array

(

[0] => Array

(

[0] => 374

[1] => <?php

[2] => 1

)

[1] => Array

(

[0] => 317

[1] => echo

[2] => 2

)

[2] => Array

(

[0] => 377

[1] =>

[2] => 2

)

[3] => Array

(

[0] => 310

[1] => $_GET

[2] => 2

)

[4] => (

[5] => Array

(

[0] => 316

[1] => 'info'

[2] => 2

)

[6] => )

[7] => ;

[8] => Array

(

[0] => 377

[1] =>

[2] => 2

)

[9] => Array

(

[0] => 376

[1] => ?>

[2] => 3

)

)

处理后的tokens:Array

(

[0] => Array

(

[0] => 317

[1] => echo

[2] => 2

)

[1] => Array

(

[0] => 310

[1] => $_GET

[2] => 2

)

[2] => (

[3] => Array

(

[0] => 316

[1] => 'info'

[2] => 2

)

[4] => )

[5] => ;

[6] => ;

)

2.2 parse扫描

获取了需要扫描的PHP tokens,下一步就是进行最关键的风险扫描了,风险扫描主体函数在lib/scanner.php文件中的parse()方法。该方法中会遍历2.1中生成的tokens,对tokens进行逐个扫描,根据每个token是否为数组(is_array)分别进行操作,由于整体代码比较庞杂,此处挑选处理上的几个关键点,并结合实际的代码,对其扫描的方式进行探究。下面先展示本次测试使用的源码,主要包含两个文件commond_exec.php与para.php两个文件,源码如下所示:commond_exec.php:

include('para.php');

$str = 'command';

$command = para($str);

shell_exec( $command );

?>

para.php:

function para($str){

return $_GET($str);

}

?>

扫描的目标文件是commond_exec.php,此时其生成的tokens如下所示:Array

(

[0] => Array

(

[0] => 262

[1] => include

[2] => 2

)

[1] => (

[2] => Array

(

[0] => 318

[1] => 'para.php'

[2] => 2

)

[3] => )

[4] => ;

[5] => Array

(

[0] => 312

[1] => $str

[2] => 4

)

[6] => =

[7] => Array

(

[0] => 318

[1] => 'command'

[2] => 4

)

[8] => ;

[9] => Array

(

[0] => 312

[1] => $command

[2] => 5

)

[10] => =

[11] => Array

(

[0] => 310

[1] => para

[2] => 5

)

[12] => (

[13] => Array

(

[0] => 312

[1] => $str

[2] => 5

)

[14] => )

[15] => ;

[16] => Array

(

[0] => 310

[1] => shell_exec

[2] => 6

)

[17] => (

[18] => Array

(

[0] => 312

[1] => $command

[2] => 6

)

[19] => )

[20] => ;

[21] => ;

)

对tokens进行遍历时,如果该token的类型是数组,那么分别获取该数组中的每个值,如下所示:$token_name = $this->tokens[$i][0];     // 该token的名称,相当于变量名称

$token_value = $this->tokens[$i][1];    // 该token的值,相当于变量的值

$line_nr = $this->tokens[$i][2];            // token出现在源码的第几行

2.2.1 文件包含处理

对token进行逐个扫描时,第一个出现的token就是便是include函数,RIPS遇到这个函数时会根据文件包含出现的位置,获取被包含文件的tokens,插入到原tokens语句的后面,其具体的操作代码如下所示:$tokenizer = new Tokenizer($try_file);

$inc_tokens = $tokenizer->tokenize(implode('',$inc_lines));

unset($tokenizer);

// if(include('file')) { - include tokens after { and not into the condition :S

if($this->in_condition)

{

$this->tokens = array_merge(

array_slice($this->tokens, 0, $this->in_condition+1), // before include in condition

$inc_tokens, // included tokens

array(array(T_INCLUDE_END, 0, 1)), // extra END-identifier

array_slice($this->tokens, $this->in_condition+1) // after condition

);

} else

{

// insert included tokens in current tokenlist and mark end

$this->tokens = array_merge(

array_slice($this->tokens, 0, $i+$skip), // before include

$inc_tokens, // included tokens

array(array(T_INCLUDE_END, 0, 1)), // extra END-identifier

array_slice($this->tokens, $i+$skip) // after include

);

}

最后生成的包含include文件的tokens如下所示,对比下会发现5-19的token是新添加的,为被包含文件para.php的tokens。Array

(

[0] => Array

(

[0] => 262

[1] => include

[2] => 2

)

[1] => (

[2] => Array

(

[0] => 318

[1] => 'para.php'

[2] => 2

)

[3] => )

[4] => ;

[5] => Array

(

[0] => 337

[1] => function

[2] => 2

)

[6] => Array

(

[0] => 310

[1] => para

[2] => 2

)

[7] => (

[8] => Array

(

[0] => 312

[1] => $str

[2] => 2

)

[9] => )

[10] => {

[11] => Array

(

[0] => 339

[1] => return

[2] => 3

)

[12] => Array

(

[0] => 312

[1] => $_GET

[2] => 3

)

[13] => (

[14] => Array

(

[0] => 312

[1] => $str

[2] => 3

)

[15] => )

[16] => ;

[17] => }

[18] => ;

[19] => Array

(

[0] => 380

[1] => 0

[2] => 1

)

[20] => Array

(

[0] => 312

[1] => $str

[2] => 4

)

[21] => =

[22] => Array

(

[0] => 318

[1] => 'command'

[2] => 4

)

[23] => ;

[24] => Array

(

[0] => 312

[1] => $command

[2] => 5

)

[25] => =

[26] => Array

(

[0] => 310

[1] => para

[2] => 5

)

[27] => (

[28] => Array

(

[0] => 312

[1] => $str

[2] => 5

)

[29] => )

[30] => ;

[31] => Array

(

[0] => 310

[1] => shell_exec

[2] => 6

)

[32] => (

[33] => Array

(

[0] => 312

[1] => $command

[2] => 6

)

[34] => )

[35] => ;

[36] => ;

)

2.2.2 添加数据源(source点)

当扫描到第11个token return时,此时会判断返回的语句是否是用户可以控制的语句,如果这条语句是用户能够控制的语句,比如此处使用$_GET进行赋值表明是用户可以控制的语句;也就是说para()方法的返回值是用户可以控制的,那么该方法返回的数据将被认为是一个被污染的数据源,即source点并将该方法添加到source_functions的数组中。对于return返回参数是否是用户可控制的判断,主要是通过函数scan_parameter()实现的,下面抽取几个关键点来了解判断流程的实现,当遇到token为return的语句时,会向后遍历token,直到该语句结束,代码的实现上是通过“;”是否出现进行判断,如下所示:while( $this->tokens[$i + $c] !== ';' )

对于每个token,判断该token是否是一个数组,如果是一个数组则检查数组元素是否是一个变量,如下所示:if( is_array($this->tokens[$i + $c]) )

{

if( $this->tokens[$i + $c][0] === T_VARIABLE )

如果该token是一个数组且为变量,则使用scan_parameter()函数对其进行检查,该函数调用形式如下。该调用的参数比较多,但是本例中实际起到判断作用的只有第三个参数,即这个token本身:$this->tokens[$i+$c],具体的值为:tokens[12],即$_GET函数。$new_find = new VulnTreeNode();

$userinput = $this->scan_parameter(

$new_find,

$new_find,

$this->tokens[$i+$c],

$this->tokens[$i+$c][3],

$i+$c,

$this->var_declares_local,

$this->var_declares_global,

false,

$GLOBALS['F_SECURES_ALL'],

TRUE

);

由于$_GET函数为定义的source函数,因此将直接认为返回值是用户可输入的,即$userinput=true。最后将此函数名添加到source_functions列中,以后的扫描该函数将作为source点看待。if($userinput == 1 || $GLOBALS['userfunction_taints'])

{

$this->source_functions[] = $this->function_obj->name;

}

2.2.3 添加风险点(sink点)

此处实际是RIPS的一个误报,RIPS将$_GET()作为可变函数名对待,如果函数名可变那么就可以将该函数名赋值为eval,从而造成代码执行的漏洞,sink点的添加也是在scan_parameter()中进行。由于此处是$_GET(),显然此函数包含在source函数中,因此使用scan_parameter()函数其返回值肯定为true,那么在函数内部将会触发如下代码块的执行。if($this->in_function && !$return_scan)

{

$this->addtriggerfunction($mainparent);

}

触发后主要执行的函数是addtriggerfunction(),该函数的作用主要是向$GLOBALS变量中添加该函数。$GLOBALS['user_functions'][$this->file_name][$this->function_obj->name][0][0] = 0;

// no securings

$GLOBALS['user_functions'][$this->file_name][$this->function_obj->name][1] = array();

// doesnt matter if called with userinput or not

$GLOBALS['user_functions'][$this->file_name][$this->function_obj->name][3] = true;

最后在包含文件扫描结束时,即token="}",此处是第17个token,将被全局变量合并到scan_functions中,即添加到sink点。if(isset($GLOBALS['user_functions'][$this->file_name]))

{

$this->scan_functions = array_merge($this->scan_functions, $GLOBALS['user_functions'][$this->file_name]);

}

2.2.4 命令执行(shell_exec)漏洞

这个漏洞是本代码中实际包含的一个漏洞,在上面各种准备工作完成后,来看一下这个实际漏洞的扫描流程;当token为shell_exec时,由于该函数是一个危险函数,即包含在sink点,那么分析将直接跳转到TAINT ANALYSIS中进行。同2.2.2类似,会跳转到scan_parameter()函数中对函数的参数进行分析,确定该参数是否是用户可控制的,即包含在source点内。该函数的参数是变量$command,该参数是一个自定义变量,RIPS对于自定义变量会进行自动扫描并通过函数variable_add()添加到var_declares_local、var_declares_global两个变量中的一个。

下面先对variable_add()函数进行简单介绍,当遍历到tokens[24],$command的赋值操作时,会触发该函数的执行。该函数调用形式如下,其中比较关键的是第二个参数,调用Analyzer::getBraceEnd()静态方法,获取该变量声明的所有token,此处$command的token的序列号为24-30,将这些tokens存储到一个数组中,最后将该变量的相关信息存入var_declares_global数组中。这样就完成了对一个文件中的全局遍历的发现及存储。$this->variable_add(

$token_value,

array_slice($this->tokens, $i-$c, $c+Analyzer::getBraceEnd($this->tokens, $i)),

'',

0, 0,

$line_nr,

$i,

isset($this->tokens[$i][3]) ? $this->tokens[$i][3] : array()

);

由于存储了该变量的tokens信息,那么对于自定义变量的分析,就转变成了对该变量的tokens的分析,遍历该变量的tokens,如果该token来自用户可控制的输入,即sorce点数据源,那么表明自定义变量的也是可控的,此处的source点就是自添加的函数para(),这样就存在一个用户可控制的数据源(source)流向危险函数(sink),形成了一个漏洞触发的完整路径。for($i=$var_declare->tokenscanstart; $itokenscanstop; $i++)

{ ...

else if( in_array($tokens[$i][1], $this->source_functions) )

{

$userinput = true;

$var_trace->marker = 4;

$mainparent->title = 'Userinput returned by function '.$tokens[$i][1].'() reaches sensitive sink.';

3、结语

上面对于RIPS的源码进行了简单的分析,从中可以看出,其工作的流程大致为遍历token,发现sink点,然后对sink点的参数使用scan_parameter进行后向追踪,如果这个参数是用户可控制的参数,及包含在source点中,那么就存在一条从source到sink的联通路径,及存在一条漏洞触发的路径,则认为是一个风险点。

* 本文作者:nightmarelee,转载请注明来自FreeBuf.COM

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

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

相关文章

投资股权众筹项目,至少需要关注6个方面

1.股权结构去工商局查看这个公司的股权结构&#xff0c;哪些人是股东&#xff0c;历史出资情况。工商局网站上有很多信息可以查看&#xff0c;我发现北京工商局网站上可以查看的信息比较全面&#xff0c;而上海的就很稀烂。 几个查询网站&#xff1a; 北京企业信用信息网 ht…

python支持gui编程_Python GUI编程完整示例

本文实例讲述了python gui编程。分享给大家供大家参考&#xff0c;具体如下&#xff1a;import osfrom time import sleepfrom tkinter import *from tkinter.messagebox import showinfoclass dirlist(object):def __init__(self, initdirnone):self.top tk()self.label lab…

左右伸缩_冬季装修为啥要留伸缩缝?等到天热地板开裂就晚了!合肥人注意下...

后台有粉丝问&#xff1a;冬天真的不适合做装修吗&#xff1f;假的&#xff0c;这句话不要再传了。每个季节都会有利有弊&#xff0c;只不过冬季施工过程中干燥的比较快&#xff0c;装修的时候对施工要求的更严格。就拿伸缩缝来说吧&#xff0c;冬天装修时不注意&#xff0c;等…

转折点(心得)

也许是刚开学不适应&#xff1f;反正就是心不静&#xff0c;给自己一天时间调整&#xff0c;马上好过来&#xff0c;加油!!!以后每天至少一道题&#xff0c;但是要是学到东西的题&#xff0c;加油&#xff01;合理安排&#xff0c;有条不紊&#xff01;转载于:https://www.cnbl…

php进入文件目录,php文件目录操作

新建文件1、先确定要写入文件的内容$content 你好;2、打开这个文件(系统会自动建立这个空文件)//假设新建的文件叫file.txt&#xff0c;而且在上级目录下。w表示‘写文件’&#xff0c;$fp下面要用到&#xff0c;表示指向某个打开的文件。$fp fopen(../file.txt, w);3、将内容…

python设计自定义栈类_Python如何自定义模块?Python基础教程,第十讲,自定义模块...

学完此次课程&#xff0c;我能做什么&#xff1f;通过此次课程&#xff0c;我们将学会如何自定义自己的模块和包&#xff0c;以及如何引用到自己的项目中&#xff0c;从而实现代码的复用。学习此次课程&#xff0c;需要多久&#xff1f;5-10分钟课程内容什么是Python的标准库&a…

windows.h和winsock2.h包含顺序问题(转)

windows.h和winsock2.h有类型重定义我是知道的&#xff0c;本来就一个库来说没问题&#xff0c;把winsock2放到windows.h前或先定义WIN32_LEAN_AND_MEAN都能解决问题但现的出了问题&#xff0c;自己写的一个库里要用到ws2tcpip&#xff0c;这个头文件第一个包含的头文件就是win…

java中软填空面试题,通过这9个Java面试题,就可以入职华为啦

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼1&#xff0e;Java程序的种类有(BCD )A&#xff0e;类(Class) B&#xff0e;Applet C&#xff0e;Application D&#xff0e;Servlet2&#xff0e;下列说法正确的有( BCD)A&#xff0e; 环境变量可在编译source code时指定B&#x…

红色警戒2修改器原理百科(七)

&#xff08;十五&#xff09;科技全开——前方高能&#xff0c;BOOM 其实这一小节&#xff0c;最晚应该在第四篇的十小节来讲述的&#xff0c;这样是按照一条线索来讲的&#xff0c;容易理解和把握笔者修改时的思路&#xff0c;以一点逐渐扩大。由于笔者的一些原因&#xff08…

python两个线程交替打印_三线程按顺序交替打印ABC的四种方法

建立三个线程A、B、C&#xff0c;A线程打印10次字母A&#xff0c;B线程打印10次字母B,C线程打印10次字母C&#xff0c;但是要求三个线程同时运行&#xff0c;并且实现交替打印&#xff0c;即按照ABCABCABC的顺序打印。二、Synchronized同步法1、基本思路使用同步块和wait、noti…

支付宝支付php代码示例,Laravel使用支付宝进行支付的示例代码

/*支付*/public function pay(){header("content-type:text/html;charsetutf8");//echo 1;die;// ******************************************************配置 start*******************************************************************************************…

关于开发中的常用手段(个人建议)

由于最近在一家外包公司 做项目 &#xff08;团队开发 3个人&#xff09; 有个朋友一起 我们研究出一套 相对来说 速度和效率都比较高的开发方法。 1. 搭建界面 对于一个程序来说 无疑刚上来就要看原型 看UI 看切图 所以界面是首要做的 那么我来说下做界面的常用方法仅个人看法…

wpf click事件在触摸屏上点击第一次没反应_你的PLC和你的触摸屏为什么总是通讯不上?...

三菱PLC全系列资料免费送啦&#xff01;想要撩我(点击上方红字&#xff0c;免费领资料)很多师傅有以下这些问题&#xff0c;其实除去厂家自行定义的通讯方式外&#xff0c;我们在做项目的时候最常用的通讯方式&#xff0c;通讯不上无非就是这几个原因是否选择正确通讯协议(modb…

php索引数组相等,php二维数组中子数组的某一键相等,其余键值求和

需要处理的数组Array([0] > Array([uid] > 1[price] > 85.00[earnings] > 40.00)[1] > Array([uid] > 8[price] > 56.00[earnings] > 31.00)[2] > Array([uid] > 15[price] > 56.00[earnings] > 31.00)[3] > Array([uid] > 15[price…

ubuntu 开放端口_Masscan端口扫描工具使用小手册

1Masscan介绍1.1 Masscan简介1) nmap是端口扫描仪的合法王位&#xff0c;而今天仍然是最通用的选择。但对于纯粹的速度也有一些已经超越了它&#xff0c;包括scanrand&#xff0c;unicornscan&#xff0c;zmap&#xff0c;和现在的masscan(https://github.com/robertdavidgraha…

MFC类的结构

1. CObject类&#xff0c;MFC库中绝大部分类的基类&#xff0c;封装了MFC中的最基本机制。 运行时类信息机制/动态创建机制/序列化机制等。。。 2. CCmdtarget - 消息映射机制最基类 3. CWinThread/CWinApp- 应用程序类&#xff0c;负责整个程序的执行流程。 4. CDocment及子类…

php base64安全吗,php base64

场景&#xff1a;PHP 用base64兑现加密解密PHP 用base64实现加密解密php中用base64实现加密解密: base64_encode() 和 base64_decode() 进行加密和解密.语法: string base64_encode ( string data )使用 base64 对 data 进行编码。设计此种编码是为了使二进制数据可以通过…

hdu4277 暴力

hdu4277 暴力 USACO ORZ Time Limit : 5000/1500ms (Java/Other) Memory Limit : 32768/32768K (Java/Other) Total Submission(s) : 4 Accepted Submission(s) : 2 Problem DescriptionLike everyone, cows enjoy variety. Their current fancy is new shapes for pastu…

多线程设置操作时间间隔_听说,有位Java程序员学完这59道多线程面试题之后,直接面上阿里...

前言多线程是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程&#xff0c;进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。软件多线程&…

在php中源代码怎么查看,查看PHP Closure的源代码

您可以从PHP获得的是有限的,使用反射您只需获取函数的参数签名以及源代码文件的开始和结束行.我曾经写过一篇博客文章&#xff1a;http://www.metashock.de/2013/05/dump-source-code-of-closure-in-php/ ……它引导我使用反射以下代码&#xff1a;function closure_dump(Closu…