使用GDB调试PHP代码,解决PHP代码死循环

最近在帮同事解决Swoole Server问题时,发现有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);
}

function test($index, $str)
{
	return str_replace(‘A‘, ‘B‘, $str);
}

通过ps aux得到进程ID和状态如下,使用gdb -p 进程ptrace跟踪,通过bt命令得到调用栈

htf 3834 2.6 0.2 166676 22060 pts/12 R+ 10:50 0:12 php dead_loop.php
gdb -p 3834
 (gdb) bt
#0  0x00000000008cc03f in zend_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
#1  0x00000000008cd643 in _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
#2  0x00000000008cebf7 in _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
#3  0x00000000008eda0a in _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
#4  0x00000000008fe687 in _zval_ptr_dtor_wrapper (zval_ptr=0x25849a0) at /home/htf/workspace/php-5.4.27/Zend/zend_variables.c:182
#5  0x000000000091259f in zend_hash_destroy (ht=0x7f7263f6e380) at /home/htf/workspace/php-5.4.27/Zend/zend_hash.c:560
#6  0x00000000008fe2c5 in _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
#7  0x0000000000936656 in _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
#8  0x0000000000939747 in zend_assign_to_variable (variable_ptr_ptr=0x7f7263f8e738, value=0x7f726426f6a8) at /home/htf/workspace/php-5.4.27/Zend/zend_execute.c:901
#9  0x0000000000997ee5 in ZEND_ASSIGN_SPEC_CV_VAR_HANDLER (execute_data=0x7f726d04b2a8) at /home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:33168
#10 0x000000000093b5fd in execute (op_array=0x21d58b0) at /home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:410
#11 0x0000000000901692 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /home/htf/workspace/php-5.4.27/Zend/zend.c:1315
#12 0x000000000087926a in php_execute_script (primary_file=0x7ffffe0038d0) at /home/htf/workspace/php-5.4.27/main/main.c:2502
#13 0x00000000009a32e3 in do_cli (argc=2, argv=0x7ffffe004d18) at /home/htf/workspace/php-5.4.27/sapi/cli/php_cli.c:989
#14 0x00000000009a4491 in main (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
#1  0x00000000008ceb86 in _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
#2  0x0000000000911d85 in _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
#3  0x00000000007767e1 in zif_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
#4  0x000000000093c03e in zend_do_fcall_common_helper_SPEC (execute_data=0x7f726d04b2a8) at /home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:643
#5  0x00000000009400e6 in ZEND_DO_FCALL_SPEC_CONST_HANDLER (execute_data=0x7f726d04b2a8) at /home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:2233
#6  0x000000000093b5fd in execute (op_array=0x21d58b0) at /home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:410

两次的BT信息不一样,这是因为程序在不同的位置中断。看到execute (op_array=0x21d58b0) 这一行,这里就是PHP执行op_array的入口了。gdb下输入f 6,(通过调用栈编号可得)。


(gdb) f 6
#6  0x000000000093b5fd in execute (op_array=0x21d58b0) at /home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:410
410			if ((ret = OPLINE->handler(execute_data TSRMLS_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文件的。然后输入f 0进入当前位置。


(gdb) p **executor_globals.opline_ptr
$4 = {handler = 0x93ff9c <zend_do_fcall_spec_const_handler>, 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 ‘<‘, op1_type = 1 ‘\001‘, op2_type = 8 ‘\b‘, result_type = 4 ‘\004‘}
</zend_do_fcall_spec_const_handler>

这里的lineno表示OPCODE所在的代码行数,可以到对应文件里去看下是哪行代码。

使用GDB可以查看到更多的信息,这里就不再一一介绍了,有兴趣各位可以自行尝试。

使用GDB调试PHP代码,解决PHP代码死循环

时间: 2024-08-08 04:49:55

使用GDB调试PHP代码,解决PHP代码死循环的相关文章

vi/vim使用进阶: 在VIM中使用GDB调试 – 使用vimgdb

vi/vim使用进阶: 在VIM中使用GDB调试 – 使用vimgdb << 返回vim使用进阶: 目录 本节所用命令的帮助入口: :help vimgdb 在UNIX系统最初设计时,有一个非常重要的思想:每个程序只实现单一的功能,通过管道等方式把多个程序连接起来,使之协同工作,以完成更强大的功能.程序只实现单一功能,一方面降低了程序的复杂性,另一方面,也让它专注于这一功能,把这个功能做到最好.就好像搭积木一样,每个积木只提供简单的功能,但不同的积木垒在一起,就能搭出大厦.汽车等等复杂的东西.

20145311利用gdb调试汇编代码

利用GDB调试汇编代码 首先编写c语言原代码,我使用的是同学分析过的代码 #include<stdio.h>short addend1 = 1;static int addend2 = 2;const static long addend3 = 3; static int g(int x){    return x + addend1;} static const int f(int x){    return g(x + addend2);} int main(void){    retur

使用gdb调试c/c++代码

转自 http://blog.csdn.net/haoel/article/details/2879 GDB概述———— GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具.或许,各位比较喜欢那种图形界面方式的,像VC.BCB等IDE的调试,但如果你是在UNIX平台下做软件,你会发现GDB这个调试工具有比VC.BCB的图形化调试器更强大的功能.所谓“寸有所长,尺有所短”就是这个道理. 一般来说,GDB主要帮忙你完成下面四个方面的功能: 1.启动你的程序,可以按照你的自定义的要求随心所

gdb调试技巧 找到php执行进程当前执行的代码

假设线上有一段php脚本,突然在某天出问题了,不处理但是进程没有退出.这种情况可能是异常休眠或者是有段死循环代码,但是我们怎么定位呢,我们这个时候最想知道的应该是这个脚本在此刻在做什么吧.这个是gdb+zbacktrace就有用了 首先随便写一个测试脚本test.php,里面就写一个sleep函数,换成死循环也可以. <?php function Mecho($i){ echo $i.PHP_EOL; } $i = 20; while($i>0){ if($i%2==0){ Mecho($i)

解决GDB调试时“ No symbol table is loaded. Use the &quot;file&quot; command”问题

1.解决的办法 : GDB调试时使用list命令时出现 “No symbol table is loaded. Use the "file" command”提示,其实是C编译器的选项CFLAGS没有设置好,gcc -o进行编译时不用不用写“-g”选项,只需在makefile文件开头写上“CFLAGS = -g”在编译时编译器就会自动使用CFLAGS的设置了. 2.探索的过程: 我的makefile文件是这样编写的: “make clear”.“make main”.“gdb main

解决gdb调试报错“No symbol table is loaded. Use the &quot;file&quot; command.”

linux下写的Makefile,然后编译成文件,gdb调试的时候用list命令报错.搞了半天,在网上找了很多,说是一定要用-g, 然而我是添加的,后来又有人说是需要把-g写调宏定义CFLAGS = -g,但是还是不对.最后经过自己的摸索,才发现,如果 是.c的文件,用CFLAGS是可以的,但是如果是.cpp文件,则必须使用CPPFLAGS = -g才可以 贴上代码如下 CPP = g++ ##这里用gcc或者是g++都没有影响 CPPFLAGS = -g# -D_REENTRANT -DDEB

解决Python代码编码问题 SyntaxError: Non-UTF-8 code starting with &#39;\xc1&#39;

导致出错的根源就是编码问题. 解决方案是: 在程序最上面加上: view plai# coding=gbk 这样程序就可以正常运行了. 解决Python代码编码问题 SyntaxError: Non-UTF-8 code starting with '\xc1'

OD调试9—实例:深入分析代码完成软件破解

OD调试9—实例:深入分析代码完成软件破解  爆破,是最初级的解决方案,不到万不得已,我们不直接修改JNZ通关.因为这样子的话,我们就享受不到破解.逆向的真正乐趣了. 了解程序背后按照剧情发展经常会出的一些走法,逆向程序的算法,才能体会逆向的真正乐趣! 所以,今天的主题是:是男人,就跟进去吧~ 这次还是使用一个比较简单的程序,不然太复杂的话,一步步跟进程序可能会把自己搞崩溃,脑子恐怕不够搞.大家凭自己的能力,能进入多少就进入多少,一分不少,一寸不多!能力是培养出来的,耐心! 试验软件:MrBil

WPF Event 在 Command 中的应用初级篇,支持所有Event 展示松耦合设计的全部代码 - 解决TextBoxBase.TextChanged或者TextBox.TextChanged等类似事件绑定问题。

做过WPF开发的人,都知道做MVVM架构,最麻烦的是Event的绑定,因为Event是不能被绑定的,同时现有的条件下,命令是无法替代Event.而在开发过程中无法避免Event事件,这样MVVM的架构就不能完全实现了. 所以后来微软提供了一个折中的方案,使用Trigger触发器和System.Windows.Interactivity结合通过事件绑定事件,个人觉得这个方法也挺可以的. 还有Prism框架方法,我看过但是这个方法比较繁琐可用性不高. 后来通过搜索和自己研究知道了一个解决方案,可以绑