用 Xdebug 修正 PHP 應用程序中的錯誤

轉自:IBM中國  author:Martin Streicher ([email]martin.streicher@gmail.com[/email]), 主編, McClatchy Interactivephp

使用 PHP 調試器比結合使用 echo 與 var_dump()、debug_zval_dump() 和 print_r() 的效果更佳linux

PHP 的 Xdebug 擴展能夠幫助您在程序出錯或失敗時剖析應用程序以查找緣由。經過本文了解如何使用 Xdebug 跟蹤調用堆棧、分析內存使用狀況並查看參數和變量的內容。

雖然您可使用 PHP 爲系統管理和傳統數據處理之類的任務建立命令行腳本,可是編程語言對 Web 應用程序的性能有主要影響。在使用過程當中,每一個 PHP 應用程序都駐留在服務器上,而且將經過代理(例如 Apache)調用 PHP 應用程序處理到來的請求。對於每一個請求,典型的 PHP Web 應用程序在簡短運行後將獲得一個 Web 頁面或 XML 數據結構。數據庫

假定通過簡單的運行後,一個分層構造的 Web 應用程序 —— 包括客戶機、網絡、HTTP 服務器、應用程序代碼和底層數據庫 —— 將會很難隔離 PHP 代碼中的錯誤。即便假定除了 PHP 代碼之外全部層均可以正常運行,跟蹤 PHP 代碼中的錯誤也會很是難,尤爲是在應用程序利用較多的類時更是如此。編程

PHP 語句 echo 和函數 var_dump()、debug_zval_dump() 和 print_r() 都是常見且流行的調試輔助工具,能夠幫助解決多種問題。可是,這些語句 —— 甚至更健壯的工具,例如 PEAR Log package —— 都是取證工具,必須在上下文環境以外先進行推測分析才能生成證據。vim

在某種程度上,經過推論進行調試是一種蠻幹的作法。收集並篩選數據,嘗試推論出發生的問題。若是缺乏重要信息,則必須從新測試代碼、重複執行步驟,而後從新開始研究。一種更加高效的方法是 程序運行時探測應用程序。您能夠對請求參數分類,篩選過程調用堆棧,並查詢任何所需的變量或對象。您能夠暫時中斷應用程序而且能夠在變量更改值時收到警報。在某些狀況下,您能夠經過交互式詢問 「若是……會怎樣?」 問題來實際影響變量。數組

稱爲調試器 的特殊應用程序支持這種 「實時的」 或交互式的檢查。調試器可能啓動並鏈接到進程上以便控制進程並監測其內存。或者,在使用解釋語言的狀況下,調試器能夠直接解釋代碼。典型的現代圖形化調試器能夠索引並瀏覽代碼,以符合人類閱讀習慣的形式輕鬆地顯示覆雜的數據結構,並同時顯示程序狀態,如調用堆棧、中間輸出和全部變量的值。例如,調試器一般都會把類的屬性和方法分類並進行描述。瀏覽器

在本文和下一篇文章中,我將介紹的工具必定可以簡化 PHP 調試。下一次,我將主要介紹交互式調試和 Zend Debugger —— 一個特別針對 PHP 的健壯調試器 —— 並探究它提供的許多功能。(Zend Debugger 是一款商業產品,是 Zend PHP 集成開發環境(IDE)的一部分)。我還將介紹一款開源 PHP 調試器,以避免您只願把錢花在啤酒上,而不是花在代碼上。可是,本文將主要介紹如何更好地取證。緩存

相似《犯罪現場調查》,只是更使人討厭服務器

代碼出錯、未能生成某個所需結果或者完全崩潰時,您須要回答四個 w 問題:where、what、why 和 when: 網絡

  • 「where」 是應用程序最後一次正常運行時所在的文件和行號。
  • 「what」 是犯錯的代碼 —— 好比說,嫌疑犯。
  • 「why」 是錯誤的本質。可能它是一個邏輯錯誤和/或與操做系統進行交互所致使的錯誤,或二者兼具。
  • 而 「when」 是出現錯誤時的上下文。在程序終止前發生了什麼狀況?像在全部犯罪行爲中同樣,若是您能夠收集到足夠的線索,那麼線索就能夠幫助您找到×××。

一種取證工具 Xdebug(上一篇文章中使用的工具,用於分析 PHP 應用程序性能),如名稱所示,將提供幾個說明程序狀態的功能,而且是應當添加到指令系統中的價值頗高的研究工具(請參閱 參考資料)。安裝後,Xdebug 將阻止無限次遞歸(表面上是這樣)、修正關於堆棧跟蹤和函數跟蹤的錯誤消息以及監視內存分配,並提供其餘功能。Xdebug 還包括一組函數,您能夠將這組函數添加到代碼中以進行運行時錯誤診斷。

例如,下面的代碼將使用一些 xdebug_...() 步驟測試 callee() 函數,以便輸出調用程序的具體位置,包括文件名、行號和調用函數的名稱。

清單 1. 測試 callee() 函數的步驟

<?php function callee( $a ) { echo sprintf("callee() called @ %s: %s from %s", xdebug_call_file(), xdebug_call_line(), xdebug_call_function() ); } $result = callee( "arg" ); ?>

這段代碼將生成:

callee() called @ /var/www/catalog/xd.php: 10 from {main}


構建和安裝 Xdebug

Xdebug 能夠很輕鬆地從 UNIX® 類操做系統(包括 Mac OS X)中的源代碼構建。若是是在 Microsoft® Windows® 上使用 PHP,則能夠從 Xdebug Web 站點下載最新 PHP 版本的二進制 Xdebug 模塊(請參閱 參考資料)。

讓咱們來構建和安裝適用於 Debian 「Sarge」 Linux® 和 PHP V4.3.10-19 的 Xdebug。在撰寫本文時,Xdebug 的最新版本是 V2.0.0RC4,發佈於 2007 年 5 月 17 日。要繼續本文,必須擁有 phpize 和 php-config 實用程序,而且必須可以編輯系統的 php.ini 配置文件(若是沒有實用程序,請訪問 PHP.net 以得到如何從頭構建 PHP 的源代碼和說明)。請執行如下步驟:

  1. 下載 Xdebug tarball(一個用 gzip 壓縮的 .tar 歸檔文件)。wget 命令能夠幫助您輕鬆地完成此操做: $ wget [url]http://www.xdebug.org/files/xdebug-2.0.0RC4.tgz[/url]
  2. 解壓縮該 tarball 並切換到源代碼目錄: $ tar xzf xdebug-2.0.0RC4.tgz $ cd xdebug-2.0.0RC4
  3. 運行 phpize 以準備適用於您的 PHP 版本的 Xdebug 代碼: $ phpize Configuring for: PHP Api Version: 20020918 Zend Module Api No: 20020429 Zend Extension Api No: 20021010

    phpize 的輸出是一個腳本 —— 一般名爲配置 —— 用於調整其他的構建過程。

  4. 運行配置腳本: $ ./configure checking build system type... i686-pc-linux-gnu checking host system type... i686-pc-linux-gnu checking for gcc... gcc checking for C compiler default output file name... a.out checking whether the C compiler works... yes checking whether we are cross compiling... no checking for suffix of executables... checking for suffix of object files... o ... checking whether stripping libraries is possible... yes appending configuration tag "F77" to libtool configure: creating ./config.status config.status: creating config.h
  5. 經過運行 make 構建 Xdebug 擴展: $ make /bin/sh /home/strike/tmp/xdebug-2.0.0RC4/libtool --mode=compile gcc -I. -I/home/strike/tmp/xdebug-2.0.0RC4 -DPHP_ATOM_INC -I/home/strike/tmp/xdebug-2.0.0RC4/include -I/home/strike/tmp/xdebug-2.0.0RC4/main -I/home/strike/tmp/xdebug-2.0.0RC4 -I/usr/include/php4 -I/usr/include/php4/main -I/usr/include/php4/Zend -I/usr/include/php4/TSRM -DHAVE_CONFIG_H -g -O0 -c /home/strike/tmp/xdebug-2.0.0RC4/xdebug.c -o xdebug.lo mkdir .libs ... Build complete. (It is safe to ignore warnings about tempnam and tmpnam).

    使用 make 將生成 Xdebug 擴展 xdebug.so。

  6. 安裝該擴展: $ sudo make install Installing shared extensions: /usr/lib/php4/20020429/

    繼續以前,使用鼠標選擇並複製上一條命令顯示的目錄。該路徑對於最後一步配置擴展相當重要。

  7. 在您喜歡的文本編輯器中打開 php.ini 文件,而後添加如下代碼: zend_extension = /usr/lib/php4/20020429/xdebug.so xdebug.profiler_enable = Off xdebug.default_enable = On

    第一行將裝入 Xdebug 擴展;第二行將禁用 Xdebug 的分析器功能(只是爲了簡單起見),而第三行將啓用擴展的調試功能。

要檢驗 Xdebug 擴展是否已經安裝並啓用,請從新啓動 Web 服務器,而後用代碼 <?php phpinfo(); ?> 建立簡單的一行 PHP 應用程序。若是將瀏覽器指向文件 —— 如 [url]http://localhost/phpinfo.php[/url] —— 並向下滾動,您應當會看到相似圖 1 所示的內容。

圖 1. 檢驗 Xdebug 擴展是否已經安裝並運行
Xdebug 擴展已啓用

注:若是您在 phpinfo() 的輸出中沒有看到 Xdebug 部分,則 Xdebug 裝入失敗。Apache 錯誤日誌會列出緣由。常見錯誤包括 zend_extension 的路徑錯誤或者與其餘擴展發生衝突。例如,若是須要使用 XCache 和 Xdebug,必定要先裝入 XCache。可是,因爲 Xdebug 適於在開發時使用並假定 xdebug.so 的路徑正確,所以須要禁用其餘擴展並重試。而後您能夠從新啓用擴展以執行其餘測試,如緩存的效果。Xdebug 站點還有其餘一些故障檢修技巧。


配置 Xdebug

指令(圖 1 中大表的最左側一列)是一些能夠設定的參數,用於改變 Xdebug 擴展的行爲。可在 php.ini 文件中設置全部指令。一些指令用於配置調試工具;其餘指令用於調整分析器的操做。忽略後者,讓咱們用一些合理設置來配置 Xdebug 以幫助調試 PHP 代碼。

限制遞歸

若是應用程序使用遞歸 —— 例如,計算斐波納契數列 —— 而且終端環境不正確,應用程序會運行很長一段時間後才用盡內存或超時。您能夠設定 xdebug.max_nesting_level 參數來限定遞歸深度。例如,xdebug.max_nesting_level = 50 將把遞歸深度限定爲 50 次嵌套調用,而後將強制終止應用程序。下面演示一下,在啓用 Xdebug 的狀態下運行下列代碼:

清單 2. 限制遞歸

<?php function deep_end( ) { deep_end(); } deep_end(); ?>

函數 deep_end() 將逐行進行到最底部。Xdebug 將在 49 次函數調用後介入並獲得圖 2(順便說一句,main() 的初始調用用於啓動程序計數做爲第 1 次調用)。

圖 2. 若是調用堆棧超出限制,Xdebug 將終止執行
遞歸次數過多

若是應用程序大量使用遞歸隔離並解決較大的問題,則須要把深度相應地設定得 「更低」。不然,將 xdebug.max_nesting_level 設爲較小的值,這樣能夠更快速地捕捉失控的函數調用序列。

回答四個 w 問題

出錯時,您須要回答四個 w 問題。Xdebug 能夠當即提供全部這些信息。下面是一些有益的初始設置;您能夠隨時調整這些設置。

清單 3. 錯誤

xdebug.dump_once = On xdebug.dump_globals = On xdebug.dump_undefined = On xdebug.dump.SERVER = REQUEST_METHOD,REQUEST_URI,HTTP_USER_AGENT xdebug.dump.REQUEST=* xdebug.show_exception_trace = On xdebug.show_local_vars = 1 xdebug.var_display_max_depth = 6

xdebug.dump_once、xdebug.dump_globals、xdebug.dump_undefined 和 xdebug.dump_SUPERGLOBAL 設置(其中 SUPERGLOBAL 能夠是 COOKIE、FILES、GET、POST、REQUEST、SERVER 或 SESSION)用於控制哪些 PHP 超全局變量將被包含在全部診斷結果中。

將 xdebug.dump_globals 設爲 On 以轉儲名爲 xdebug.dump_SUPERGLOBAL 設置中的超全局變量。例如,xdebug.dump_SERVER = REQUEST_METHOD,REQUEST_URI,HTTP_USER_AGENT 將打印 PHP 超全局變量 $_SERVER['REQUEST_METHOD']、$_SERVER['REQUEST_URI'] 和 $_SERVER['HTTP_USER_AGENT']。若是須要打印超全局變量數組中的全部值,請使用星號 (*),例如 xdebug.dump_REQUEST=*。若是進一步將 xdebug.dump_undefined 設爲 On 而且不設定指定的超全局變量,則仍用值 undefined 打印變量。

即便捕捉到異常,代碼行 xdebug.show_exception_trace = On 仍將強制執行異常跟蹤。代碼行 xdebug.show_local_vars = 1 將打印每一個函數調用的最外圍中的全部局部變量,包括還沒有初始化的變量。而 xdebug.var_display_max_depth = 6 表示轉儲複雜變量的深度。

整合

清單 4 顯示了 php.ini 文件的 Xdebug 的全部相關設置。

清單 4. php.ini 文件的設置

zend_extension = /usr/lib/php4/20020429/xdebug.so xdebug.default_enable = On xdebug.show_exception_trace = On xdebug.show_local_vars = 1 xdebug.max_nesting_level = 50 xdebug.var_display_max_depth = 6 xdebug.dump_once = On xdebug.dump_globals = On xdebug.dump_undefined = On xdebug.dump.REQUEST = * xdebug.dump.SERVER = REQUEST_METHOD,REQUEST_URI,HTTP_USER_AGENT

將這些設置(或相似的內容)保存到 php.ini 文件中,而後從新啓動 Web 服務器。


解釋轉儲報告

如下示例顯示了出錯時發生的狀況。把您的 「有待改進」 的代碼修改成相似清單 5 所示的代碼。

清單 5. 修改錯誤代碼

<?php function deep_end( $count ) { // add one to the frame count $count += 1; if ( $count &lt; 48 ) { deep_end( $count ); } else { trigger_error( "going off the deep end!" ); } } // main() is called to start the program, // so the call stack begins with one frame deep_end( 1 ); ?>

若是運行這段新代碼,您應當會看到大量信息,以下所示:

圖 3. 出錯時超全局變量、堆棧和局部變量的轉儲
出錯時超全局變量、堆棧和局部變量的轉儲

傳遞給 trigger_error 的消息文本顯示在頂部。底部是受請求的 $_SERVER 元素列表和已經定義的 $_REQUEST 元素列表。最底部是 #48 範圍中的變量列表,這是根據清單對 deep_end() 進行的調用。在調用中,$count 是整數 48。當此 Xdebug 配置就緒後,您如今有更多的線索能夠跟蹤犯罪者。

下面是另一個技巧:Xdebug 提供了一個加強型 var_dump() 函數,它對於 PHP 數組和類尤其有幫助。例如,清單 6 顯示了簡單的(PHP V4)類和實例。

清單 6. PHP V4 類和實例

<?php class Person { var $name; var $surname; var $age; var $children = array(); function Person( $name, $surname, $age, $children = null) { $this->name = $name; $this-&gt;surname = $surname; $this-&gt;age = $age; foreach ( $children as $child ) { $this-&gt;children[] = $child; } } } $boy = new Person( 'Joe', 'Smith', 4 ); $girl = new Person( 'Jane', 'Smith', 6 ); $mom = new Person( 'Mary', 'Smith', 34, array( $boy, $girl ) ); var_dump( $boy, $mom ); ?&gt;

清單 7 顯示了 var_dump() 的輸出。

清單 7. var_dump() 輸出

object(person) var 'name' =&gt; string 'Joe' (length=3) var 'surname' =&gt; string 'Smith' (length=5) var 'age' =&gt; int 4 var 'children' =&gt; array empty object(person) var 'name' =&gt; string 'Mary' (length=4) var 'surname' =&gt; string 'Smith' (length=5) var 'age' =&gt; int 34 var 'children' =&gt; array 0 =&gt; object(person) var 'name' =&gt; string 'Joe' (length=3) var 'surname' =&gt; string 'Smith' (length=5) var 'age' =&gt; int 4 var 'children' =&gt; array empty 1 =&gt; object(person) var 'name' =&gt; string 'Jane' (length=4) var 'surname' =&gt; string 'Smith' (length=5) var 'age' =&gt; int 6 var 'children' =&gt; array empty

若是結合使用 Xdebug 與 PHP V5 類,轉儲包括 public、private 和 protected 之類的屬性。


 

跟蹤代碼

解決錯誤 —— 如解開神祕謀殺之謎 —— 一般要求構造詳細的時間線。例如,內存泄漏一般不會把自身代表爲一個錯誤計算。相反,操做將正常進行,直至內存用盡,而後應用程序忽然終止。若是內存泄漏因爲某些請求而惡化,可能會不斷出現錯誤而且難以預測。在內存使用量與時間之間創建映射的時間線將揭示泄漏的嚴重程度。一條精細的時間線 —— 好比,從函數到函數 —— 將進一步指出泄漏源。

Xdebug 能夠提供一條詳細的時間線進行執行跟蹤。當跟蹤被啓用後,Xdebug 將記錄全部函數調用,包括每一個函數的參數和返回值。您能夠將每一個日誌或跟蹤 的格式設爲符合人類閱讀習慣或者機器可讀的格式。您最好使用前者,雖然您可能編寫獨立而特定的應用程序來分析後者。

同轉儲同樣,Xdebug 有若干個 php.ini 選項用於自定義跟蹤內容。例如,下面一批設置將生成最詳細的輸出。

清單 8. 跟蹤自定義

xdebug.trace_format = 0 xdebug.auto_trace = On xdebug.trace_output_dir = /tmp/traces xdebug.trace_output_name = trace.%c.%p xdebug.collect_params = 4 xdebug.collect_includes = On xdebug.collect_return = On xdebug.show_mem_delta = On

設定 xdebug.auto_trace = 1 將在執行全部 PHP 腳本以前先啓用自動跟蹤。另外,您能夠經過代碼設定 xdebug.auto_trace = 0,並分別使用 xdebug_start_trace() 和 xdebug_stop_trace() 函數啓用和禁用跟蹤。可是,若是 xdebug.auto_trace 爲 1,則能夠在包括配置好的 auto_prepend_file 以前先啓動跟蹤。

選項 xdebug.trace_ouput_dir 和 xdebug.trace_output_name 用於控制保存跟蹤輸出的位置。在這裏,全部文件都被保存到 /tmp/traces 中,而且每一個跟蹤文件都以 trace 爲開頭,後接 PHP 腳本的名稱(%s)以及進程 ID(%p)。全部 Xdebug 跟蹤文件都以 .xt 後綴結尾。

默認狀況下,Xdebug 將顯示時間、內存使用量、函數名和函數調用深度字段。若是將 xdebug.trace_format 設爲 0,則輸出將符合人類閱讀習慣(將參數設爲 1 則爲機器可讀格式)。此外,若是指定 xdebug.show_mem_delta = 1,則能夠查看內存使用量是在增長仍是在減小,而若是指定 xdebug.collect_params = 4,則能夠查看傳入參數的類型和值。要監視每一個函數返回的值,請設定 xdebug.collect_return = 1。

接下來看另一個示例。建立 /tmp/traces 目錄,而後用 mkdir /tmp/traces; chmod a+rwx /tmp/traces 將其模式更改成可以被任何用戶閱讀的文件(world-readable)和可以被任何用戶寫入的文件(world-writable)(若是您不肯共享 traces 目錄,請確保至少 Web 服務器用戶 —— 一般爲 www任何人 —— 能夠將數據寫入該目錄)。將以上跟蹤設置添加到 php.ini 文件中,從新啓動 Web 服務器,而後把瀏覽器再次指向 phpinfo() 應用程序。整個跟蹤應當相似清單 9 所示:

清單 9. 整個跟蹤

TRACE START [2007-06-06 14:04:55] 0.0003 9440 +9440 -&gt; {main}() /var/www/catalog/t/info.php:0 0.0005 9440 +0 -&gt; phpinfo() /var/www/catalog/t/info.php:1 &gt;=-&gt; TRUE &gt;=-&gt; 1 0.2351 9208 TRACE END [2007-06-06 14:04:55]

在這裏,main() 將調用 phpinfo(),後者將返回 TRUE。當 main() 退出時,它將返回 1。接下來,將瀏覽器指向 「最複雜的內容」 或系統中的其餘某個 PHP 應用程序以生成更詳細的跟蹤。

清單 10 顯示了在計算第四個斐波納契數列時上一篇文章中的 PHP Fibonacci 生成器的跟蹤:

清單 10. PHP Fibonacci 生成器跟蹤

TRACE START [2007-06-06 14:17:17] 0.0004 16432 +16432 -&gt; {main}() /var/www/catalog/t/fibonacci.php:0 0.0006 16696 +264 -&gt; fib('4') /var/www/catalog/t/fibonacci.php:35 0.0007 16696 +0 -&gt; fib(3) /var/www/catalog/t/fibonacci.php:7 0.0007 16736 +40 -&gt; fib(2) /var/www/catalog/t/fibonacci.php:7 0.0007 16848 +112 -&gt; fib(1) /var/www/catalog/t/fibonacci.php:7 &gt;=&gt; 1 0.0008 16904 +56 -&gt; fib(0) /var/www/catalog/t/fibonacci.php:7 &gt;=&gt; 0 &gt;=&gt; 1 0.0009 16904 +0 -&gt; fib(1) /var/www/catalog/t/fibonacci.php:7 &gt;=&gt; 1 &gt;=&gt; 2 0.0009 16904 +0 -&gt; fib(2) /var/www/catalog/t/fibonacci.php:7 0.0009 16904 +0 -&gt; fib(1) /var/www/catalog/t/fibonacci.php:7 &gt;=&gt; 1 0.0010 16904 +0 -&gt; fib(0) /var/www/catalog/t/fibonacci.php:7 &gt;=&gt; 0 &gt;=&gt; 1 &gt;=&gt; 3 &gt;=&gt; 1 0.0011 12528 TRACE END [2007-06-06 14:17:17]

第一列顯示時間,第二列是累計的內存使用量,第三列是增長的內存使用量,而第四列顯示函數調用,包括參數。

標有 &gt;=&gt; 的行顯示每一個函數的返回值(查找相應的縮進 -&gt; 將調用與其返回值匹配起來)。此外,最後的 &gt;=&gt; 1 是 main() 的返回值。

若是使用 vim,Xdebug 的創造者 Derick Rethans 提供了專門針對 Xdebug 跟蹤的一組語法加亮提示。提示包含在 Xdebug 源代碼包內的 xt.vim 文件中。對於最近的 Linux 發行版,只需將 xt.vim 複製到 $VIMRUNTIME/syntax/xt.vim 中,而後運行 vim tracefile.xt。圖 4 顯示了 vim 中加亮的 Fibonacci 跟蹤。

圖 4. Xdebug 跟蹤的 vim 語法文件將使您能夠輕鬆地進行分析
vim 中加亮的跟蹤


結束語

跟蹤 PHP 代碼中的錯誤多是一項挑戰。可是若是您有開發系統而且能夠安裝 Xdebug,那麼更正這些錯誤就會變得輕鬆得多。Xdebug 能夠顯示堆棧跟蹤,轉儲甚爲複雜的變量,隨時間跟蹤內存使用量,並容許您在出錯或崩潰時(不是若是,而是發生時)進行有效的過後分析。

相關文章
相關標籤/搜索