使用GDB调试PHP代码,解决PHP代码死循环问题
最近在帮同事解决SwooleServer问题时,发现有1个worker进程一直处于R的状态,而且CPU耗时非常高。初步断定是PHP代码中发生死循环。
下面通过一段代码展示如何解决PHP死循环问题。
#dead_loop.php $array=array(); for($i=0;$i<10000;$i++) { $array[]=$i; } include__DIR__."/include.php"; #include.php while(1) { usleep(10); $keys=array_flip($array); $index=array_search(rand(1500,9999),$array); $str=str_repeat('A',$index); $strb=test($index,$str); } functiontest($index,$str) { returnstr_replace('A','B',$str); }
通过psaux得到进程ID和状态如下,使用gdb-p进程ptrace跟踪,通过bt命令得到调用栈
htf38342.60.216667622060pts/12R+10:500:12phpdead_loop.php gdb-p3834 (gdb)bt #00x00000000008cc03finzend_mm_check_ptr(heap=0x1eaa2c0,ptr=0x2584910,silent=1,__zend_filename=0xee3d40"/home/htf/workspace/php-5.4.27/Zend/zend_variables.c", __zend_lineno=182,__zend_orig_filename=0xee1888"/home/htf/workspace/php-5.4.27/Zend/zend_execute_API.c",__zend_orig_lineno=437) at/home/htf/workspace/php-5.4.27/Zend/zend_alloc.c:1485 #10x00000000008cd643in_zend_mm_free_int(heap=0x1eaa2c0,p=0x2584910,__zend_filename=0xee3d40"/home/htf/workspace/php-5.4.27/Zend/zend_variables.c",__zend_lineno=182, __zend_orig_filename=0xee1888"/home/htf/workspace/php-5.4.27/Zend/zend_execute_API.c",__zend_orig_lineno=437)at/home/htf/workspace/php-5.4.27/Zend/zend_alloc.c:2064 #20x00000000008cebf7in_efree(ptr=0x2584910,__zend_filename=0xee3d40"/home/htf/workspace/php-5.4.27/Zend/zend_variables.c",__zend_lineno=182, __zend_orig_filename=0xee1888"/home/htf/workspace/php-5.4.27/Zend/zend_execute_API.c",__zend_orig_lineno=437)at/home/htf/workspace/php-5.4.27/Zend/zend_alloc.c:2436 #30x00000000008eda0ain_zval_ptr_dtor(zval_ptr=0x25849a0,__zend_filename=0xee3d40"/home/htf/workspace/php-5.4.27/Zend/zend_variables.c",__zend_lineno=182) at/home/htf/workspace/php-5.4.27/Zend/zend_execute_API.c:437 #40x00000000008fe687in_zval_ptr_dtor_wrapper(zval_ptr=0x25849a0)at/home/htf/workspace/php-5.4.27/Zend/zend_variables.c:182 #50x000000000091259finzend_hash_destroy(ht=0x7f7263f6e380)at/home/htf/workspace/php-5.4.27/Zend/zend_hash.c:560 #60x00000000008fe2c5in_zval_dtor_func(zvalue=0x7f726426fe50,__zend_filename=0xeea290"/home/htf/workspace/php-5.4.27/Zend/zend_execute.c",__zend_lineno=901) at/home/htf/workspace/php-5.4.27/Zend/zend_variables.c:45 #70x0000000000936656in_zval_dtor(zvalue=0x7f726426fe50,__zend_filename=0xeea290"/home/htf/workspace/php-5.4.27/Zend/zend_execute.c",__zend_lineno=901) at/home/htf/workspace/php-5.4.27/Zend/zend_variables.h:35 #80x0000000000939747inzend_assign_to_variable(variable_ptr_ptr=0x7f7263f8e738,value=0x7f726426f6a8)at/home/htf/workspace/php-5.4.27/Zend/zend_execute.c:901 #90x0000000000997ee5inZEND_ASSIGN_SPEC_CV_VAR_HANDLER(execute_data=0x7f726d04b2a8)at/home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:33168 #100x000000000093b5fdinexecute(op_array=0x21d58b0)at/home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:410 #110x0000000000901692inzend_execute_scripts(type=8,retval=0x0,file_count=3)at/home/htf/workspace/php-5.4.27/Zend/zend.c:1315 #120x000000000087926ainphp_execute_script(primary_file=0x7ffffe0038d0)at/home/htf/workspace/php-5.4.27/main/main.c:2502 #130x00000000009a32e3indo_cli(argc=2,argv=0x7ffffe004d18)at/home/htf/workspace/php-5.4.27/sapi/cli/php_cli.c:989 #140x00000000009a4491inmain(argc=2,argv=0x7ffffe004d18)at/home/htf/workspace/php-5.4.27/sapi/cli/php_cli.c:1365
执行gdb后,死循环的进程会变成T的状态,表示正在Trace。这个是独占的,所以不能再使用strace/gdb或者其他ptrace工具对此进程进行调试。另外此进程会中断执行。gdb输入c后,程序继续向下运行。然后再次按下ctrl+c中断程序。通过bt命令查看进程的调用栈。
(gdb)bt #0_zend_mm_alloc_int(heap=0x1eaa2c0,size=72,__zend_filename=0xe43410"/home/htf/workspace/php-5.4.27/ext/standard/array.c",__zend_lineno=2719, __zend_orig_filename=0xee5a38"/home/htf/workspace/php-5.4.27/Zend/zend_hash.c",__zend_orig_lineno=412)at/home/htf/workspace/php-5.4.27/Zend/zend_alloc.c:1895 #10x00000000008ceb86in_emalloc(size=72,__zend_filename=0xe43410"/home/htf/workspace/php-5.4.27/ext/standard/array.c",__zend_lineno=2719, __zend_orig_filename=0xee5a38"/home/htf/workspace/php-5.4.27/Zend/zend_hash.c",__zend_orig_lineno=412)at/home/htf/workspace/php-5.4.27/Zend/zend_alloc.c:2425 #20x0000000000911d85in_zend_hash_index_update_or_next_insert(ht=0x2257a10,h=3972,pData=0x7ffffe0012b0,nDataSize=8,pDest=0x0,flag=1, __zend_filename=0xe43410"/home/htf/workspace/php-5.4.27/ext/standard/array.c",__zend_lineno=2719)at/home/htf/workspace/php-5.4.27/Zend/zend_hash.c:412 #30x00000000007767e1inzif_array_flip(ht=1,return_value=0x7f726424ea68,return_value_ptr=0x0,this_ptr=0x0,return_value_used=1) at/home/htf/workspace/php-5.4.27/ext/standard/array.c:2719 #40x000000000093c03einzend_do_fcall_common_helper_SPEC(execute_data=0x7f726d04b2a8)at/home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:643 #50x00000000009400e6inZEND_DO_FCALL_SPEC_CONST_HANDLER(execute_data=0x7f726d04b2a8)at/home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:2233 #60x000000000093b5fdinexecute(op_array=0x21d58b0)at/home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:410
两次的BT信息不一样,这是因为程序在不同的位置中断。看到execute(oparray=0x21d58b0)这一行,这里就是PHP执行oparray的入口了。gdb下输入f6,(通过调用栈编号可得)。
(gdb)f6 #60x000000000093b5fdinexecute(op_array=0x21d58b0)at/home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:410 410if((ret=OPLINE->handler(execute_dataTSRMLS_CC))>0){ (gdb)p*op_array $2={type=2'\002',function_name=0x7f726d086540"test",scope=0x0,fn_flags=134217728,prototype=0x0,num_args=2,required_num_args=2,arg_info=0x7f726d086bd8, refcount=0x7f726d0870f0,opcodes=0x7f726424d600,last=8,vars=0x7f726424e890,last_var=2,T=1,brk_cont_array=0x0,last_brk_cont=0,try_catch_array=0x0, last_try_catch=0,static_variables=0x0,this_var=4294967295,filename=0x7f726424ba38"/home/htf/wwwroot/include.php",line_start=12,line_end=15,doc_comment=0x0, doc_comment_len=0,early_binding=4294967295,literals=0x7f726424eae0,last_literal=4,run_time_cache=0x7f726450bfb0,last_cache_slot=1,reserved={0x0,0x0,0x0,0x0}}
这里的filename就能看到op_array是哪个PHP文件的。然后输入f0进入当前位置。
(gdb)p**executor_globals.opline_ptr $4={handler=0x93ff9c,op1={constant=1680133296,var=1680133296,num=1680133296,hash=140129283132592,opline_num=1680133296, jmp_addr=0x7f726424ccb0,zv=0x7f726424ccb0,literal=0x7f726424ccb0,ptr=0x7f726424ccb0},op2={constant=0,var=0,num=0,hash=0,opline_num=0,jmp_addr=0x0, zv=0x0,literal=0x0,ptr=0x0},result={constant=32,var=32,num=32,hash=32,opline_num=32,jmp_addr=0x20,zv=0x20,literal=0x20,ptr=0x20}, extended_value=1,lineno=5,opcode=60'
这里的lineno表示OPCODE所在的代码行数,可以到对应文件里去看下是哪行代码。使用GDB可以查看到更多的信息,这里就不再一一介绍了,有兴趣各位可以自行尝试。
zbacktrace的使用
zend官方提供了一个gdb的脚本,对指令进行了封装,可以直接看到php函数的调用关系。在php源代码包的根目录中有一个.gdbinit。使用
sourceyour_php_src_path/.gdbinit zbacktrace
可以直接看到PHP函数的调用堆栈。
以上就是本文的全部内容了,希望大家能够喜欢。