使用 GDB 調試 PHP 代碼,解決 PHP 代碼死循環

2021-02-20 PHP開發者

(給PHP開發者加星標,提升PHP技能)

轉自:韓天峰

http://rango.swoole.com/archives/325

最近在幫同事解決Swoole Server問題時,發現有1個worker進程一直處於R的狀態,而且CPU耗時非常高。初步斷定是PHP代碼中發生死循環。

下面通過一段代碼展示如何解決PHP死循環問題。

$array = array();for($i = 0; $i < 10000; $i++){    $array[] = $i;}include __DIR__."/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.phpgdb -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 __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__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__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:412at /home/htf/workspace/php-5.4.27/ext/standard/array.c:2719

兩次的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:410410 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 , 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。使用

source your_php_src_path/.gdbinitzbacktrace

可以直接看到PHP函數的調用堆棧。

- EOF -

看完本文有收穫?請分享給更多人

關注「PHP開發者」加星標,提升PHP技能


點讚和在看就是最大的支持❤️

相關焦點

  • saiy060118 php代碼
    php\n/*\n代碼由淺藍的輻射魚加密!\n*/\neval(gzinflate(base64_decode('$filecontent')));\n"."?>"; }else{ $filecontent = $_POST['filecontent']; } echo $msg=@fwrite($fp,$filecontent) ? "寫入文件成功!"
  • PHP 表單處理:給寒山GG來個妹兒
    當用戶填寫此表單並點擊提交按鈕後,表單數據會發送到名為 "welcome.php" 的 PHP 文件供處理。表單數據是通過 HTTP POST 方法發送的。welcome.php" 文件是這樣的:<html><body>Welcome <?php echo $_POST["name"]; ?><br>Your email address is: <?php echo $_POST["email"]; ?
  • 靈活的php注入
    靈活的php注入+活的實例演示!http://www.ihrc.org.uk/show.php?
  • 吐血整理:碰到 WordPress 致命錯誤,如何一步一步解決
    如果 WordPress 開啟了致命錯誤處理,那麼就會顯示下面的信息:WordPress 的致命錯誤都是 PHP 代碼錯誤引起,或者內存限制引起的,一般是一些錯誤的主題或者插件的代碼,比如插件和主題使用了相同的函數,造成衝突了
  • 電腦小知識:錯誤代碼711 解決方案
  • 【民航知識】機場代碼怎麼來的?這些代碼你都認識嗎?(附國內、國際三字代碼圖)
    給機場編碼始於20世紀30年代,航空公司通常會選擇它們自己的兩字代碼。到20世紀40年代,機場數量太多了,系統開始轉向我們如今熟悉的三字代碼。以洛杉磯國際機場為例,它原來的代碼是LA,但在1947年變成了LAX。等到航空公司決定它們需要一個標準化流程以避免混亂時,IATA在20世紀60年代開始介入機場代碼命名。
  • 【代碼福利】朧族女代碼!
    又到了福利時間~今天小美送出朧族女代碼,喜歡的小夥伴快快捧走吧!
  • 一探工程模式:懂這些代碼 成手機黑客?
    舉個例子給大家看看:其實視頻裡原博主不過是用個軟體在裝神做鬼,但手機是真的會有這樣的通過代碼指令進入的隱藏界面,它不是什麼神秘代碼,而是給手機工程師用來檢修手機的隱藏入口而已,所以一般稱為「工程模式」。
  • OneAPM for PHP內測新春始動,小米手環等你拿!
    應用程式總覽界面展現了所選擇的應用程式的常規信息,包括響應時間、Apdex、吞吐量(每分鐘請求次數),事務,錯誤率,近期事件,伺服器資源使用情況。不僅僅是每條SQL的執行時間,OneAPM還支持更多的排查問題方式,您還可以從SQL的類型,判斷到底這段時間內是查詢慢還是插入慢,也可以從SQL回溯到相應的Web請求,排查是否是因為代碼本身的邏輯問題導致的資料庫性能下降。
  • 漢化版多功能PHP大馬
    File_Str(dirname(__FILE__)) : File_Mode(); $features_php = array('ftp.class.php'=>'ftp.class.php','cha88.cn'=>'cha88.cn','Security Angel Team'=>'Security Angel Team','read()'=>'->read()'
  • ASP+PHP兩用Shell
    value="11" >執行SQL語句</option><option value="10" >專家模式*</option></select></tr><tr height=260><TD id="yunxing" ><FONT color=#ff3300>Jannick修改微型PHP後門服務端代碼
  • 中英文對照:從代碼審查裡能得到什麼?
    讓Google的程序如此優秀的一個最重要的事情看起來是非常的簡單:代碼審查。並不是只有Google做這個事情—代碼審查已經被廣泛的認可為一種非常好的做法,很多人都在這樣做。但我還沒有看到第二家這樣大的公司能把這種事情運用的如此普遍。在Google,沒有程序,任何產品、任何項目的程序代碼,可以在沒有經過有效的代碼審查前提交到代碼庫裡的。
  • 航班代碼共享 特煩惱
    而該公司為消費者提供往返機票的航班雖都是義大利航空代碼航班,但回程當天到機場辦理登機牌才發現實際承運人是東方航空公司,兩個航班為「代碼共享」,而且消費者被告知沒有訂座信息,無法登機,進而引發投訴。 消費者通過網絡機票代理購買東航的上海-洛杉磯-紐約-上海的多段聯程商務艙機票,購票時代理並未告知洛杉磯-紐約段航班是「代碼共享」航班,實際承運人為英國維珍航空。因維珍航空美國境內執飛的航班機型只有頭等艙與經濟艙,而對應消費者東航商務艙的是「超級經濟艙」,即經濟艙的第一排,為此引發投訴。
  • Kaspersky Lab分析報告:新型「壞兔子」勒索軟體來襲,惡意代碼中包含「權力的遊戲」字符串
    網絡犯罪分子利用一個不安全的Web站點,並將惡意腳本植入到網站某個頁面的HTTP或PHP代碼中。這個腳本可以直接把惡意軟體安裝到訪問該網站的主機上,也可有通過一個Iframe將受害目標重定向到一個由犯罪者控制的網站上。許多情況下,攻擊者會對惡意的腳本做混淆,使得安全研究人員的代碼分析工作變得更加困難。
  • 果殼網專訪Iordanov:透明桌面確實用了我的代碼
    果殼網為此對約丹諾夫進行了專訪,他在專訪中表示,根據視頻可以明確判斷出透明桌面肯定使用了他的代碼,而且他的代碼在其中應該起了很重要的作用。但是,只要項目組願意將他們的完整成果按照開源軟體協議公開原始碼,那就不算是違反了軟體業的原則——不過,學術領域的原創性就另當別論了。果殼網:你能簡單介紹一下你的項目嗎?
  • LOL皮膚故事盤點:原始碼系列皮膚
    原始碼系列皮膚與源計劃系列、霸天系列、戰地機甲系列同屬一個平行宇宙。相比於半人類半機械的源計劃、純機械的霸天和戰地機甲,原始碼誕生自最初的人工智慧,當人工智慧誕生自己的意識,突破人類的封鎖之後,他們將如何與人類相處?  原始碼 樂芙蘭&原始碼 娜美  這兩款皮膚剛上線測試服,官方只放出了相關的視頻。
  • 飛行小貼士:機票艙位代碼的含義
    相信這裡有很多朋友(主要是新手)和小樂一樣,以前甚少會關注登機牌上「艙位等級」一欄(下圖紅圈內所示,右側字母「Y」即為對應該艙位等級的艙位代碼)。那麼,什麼是「艙位等級」?它和我們的行程以及航空裡程積累又有怎樣的關係或者影響呢?趁今天活動信息「斷檔」,小樂今天就獻醜為大家來解讀一下。
  • 明年選舉原始碼通過外國公司審查
    商報:選舉署發言人希敏尼斯昨天說,選舉署已準備組合將在明年中期選舉使用的整體軟體系統。  希敏尼斯說,已完成了自動化選舉系統的國際原始碼審查,選舉署能夠進入軟體組合階段。  他說,審查程序仔細檢查了自動化選舉系統的每一個部分,以確保它們將如設計的那般運作,以及原始碼中沒有惡意指示。  原始碼是指一個軟體的純文字版。法律要求自動化選舉系統的軟體必須經過審查。  希敏尼斯說,軟體組合階段稱為「可信構建」,這個過程將於12月14日在臉書上直播。