跨平台PHP调试器设计及使用方法——拾遗
? ? ? ? 之前七篇博文講解了跨平臺PHP調試器從立項到實現的整個過程,并講解了其使用方法。但是它們并不能全部涵蓋所有重要內容,所以新開一片博文,用來講述其中一些雜項。(轉載請指明出于breaksoftware的csdn博客)
觸發調試的方法
? ? ? ? Xdebug提供了兩種調試方式。一種需要我們在觸發調試的URL中新增XDEBUG_SESSION_START或XDEBUG_SESSION_STOP_NO_EXEC來控制調試開啟或關閉。比如我們要調試http://192.168.41.130/var/www/html/index.php觸發的邏輯,則需要請求
http://192.168.41.130/var/www/html/index.php?XDEBUG_SESSION_START=netbeans-xdebug
? ? ? ? 調試結束后,我們需要請求下面鏈接以關閉調試
http://192.168.41.130/var/www/html/index.php?XDEBUG_SESSION_STOP_NO_EXEC=netbeans-xdebug
? ? ? ? 這種方法存在明顯的缺陷。比如我們一個待測功能頁中,我們不可能給每個觸發調試的URL增加上述標志。更不可能在每次調試后觸發一次關閉調試的請求。因為頁面中發起請求的方式和位置可能很多,每次手工去修改原始代碼也違背了我設計該調試器的初衷。我曾考慮過給待測頁面包一層框架,即我們設計一個頁面“瀏覽器”。在這個頁面瀏覽器中訪問待測頁面。待測頁面中觸發的請求被外層的頁面“瀏覽器”捕獲,并追加相關參數再發起真實請求。但是我覺得這個方案有點讓整個調試器的設計偏向于設計一款功能強大的頁面“瀏覽器”,所以它只能作為我最次方案的一種選擇。
? ? ? ? Xdebug還有另一種觸發調試的方法,就是自動觸發,即每次請求來都觸發調試行為。我們需要做的就是在配置文件中新增如下內容
xdebug.remote_autostart=On
? ? ? ? 這個方案也會有問題。就是我們在調試時往往只是關注于一兩個請求對應的處理邏輯,而往往抵達觸發這一兩個請求場景之前還會有其他請求被發起。打個比方,我們要調試讓用戶修改自己信息的接口。在此之前肯定有一次獲取該用戶已有信息的請求,然后把用戶信息顯示出來。用戶修改時,可能有些信息還要經過PHP邏輯校驗,這些也是請求。這樣在用戶保存修改信息之前已經調用了若干接口,而這些接口可能會被我們設置的斷點中斷。即使我們沒有設置斷點,也會被中斷到代碼的第一行。對于這些我們不關心的調試流程,我們可能會不停執行Run當本次調試結束。但是這么繁瑣的體驗是非常不好的。于是這就有了設計“調試開關”的必要。
? ? ? ? 在我們觸發調試前,我們調試開關關閉,這樣既省事又有效率。當我們要觸發調試時,才開啟調試開關。
Python錯誤
? ? ? ? 在一些環境下,使用Python2.7搭建和使用該調試器時,會報CTYPE= CTYPE.ENCODE(DEFAULT_ENCODING) # OMIT IN 3.X! UNICODEDECODEERROR錯誤。好在網上有很多解決方案,就直接刪掉那幾行就行。
FPM超時問題
? ? ? ? 在一些生產環境下,為了增強用戶體驗以及預防一些錯誤發生,往往會設置一些超時參數。比如PHP的FPM就可以設置超時時間。但是在開發環境下,一般這個超時可以不用設置,而且設置還會影響調試器的使用。因為我們調試一段代碼可能會消耗很多時間,沒誰可以估算出這個超時要設置多久。如果遇到這個問題的同學,可能參見《PHP超時處理全面總結》。
Pydbgp的缺陷
? ? ? ? 在探索Pydbgp庫時,我發現這個庫并非非常完善,它還存在一些缺陷。同時為了不影響它的整體結構,我基本就是打patch的思路去做修改,且要求做到最小修改以解決問題。
? ? 已結束調試Session殘留
? ? ? ? 首先我們使用session查看可調試會話ID,然后使用select指令進入調試會話并進行調試。當我們退出調試會話時,存在兩種狀態:調試已經結束(運行到代碼結尾處之后)和調試仍可進行(只是退出調試會話,該會話還有效)。Pydbgp庫存在一個問題,它會一直保存會話ID,而不管其是否已經失效。這樣隨著我們調試次數的增多,session指令獲取的會話ID會越來越多,而往往大部分都是無效的。對于我們自動選擇調試會話的調試器狀態機來說這個工作任務會越來越重,所以這個地方需要做優化。優化的方案也非常簡單,在pydbgpd.py的do_quit方法做如下修改
def do_quit(self, argv):"""quit, exit, q -- exit the DBGP Application Layer shell"""if self._app.currentSession._stop:self._app.currentSession._application.releaseSession(self._app.currentSession)
? ? 當前會話設置出錯
? ? ? ? 在調試器中,有若干會話,其中只有一個會話可能成為當前正在被調試的會話。但是原代碼中對當前會話的切換判斷存在缺陷,它沒有考慮到當前會話是否已經失效。修改點是dbgp\server.py文件中class application的addSession方法
? ? 未返回斷點ID信息
? ? ? ? 當我們設置一個斷點后,應該返回該斷點ID。我們可以通過該斷點ID去刪除它。然而Pydbgp卻將這個ID給“私吞”了。于是我們要做修改讓它放開這個數據。修改點在dbgp\server.py文件中
? ? 未返回Array和Object類型變量信息
? ? ? ? 這個問題也是非常致命的。我們查看一個變量,它可能是int型的,可能是string型的。這些基礎類型Pydbgp均作了解析和記錄。然而對于復雜類型,比如Array或者Object類型變量,Pydbgp都沒對它們進行解析。這塊功能只能我們自己寫了,我決定使用Json格式來保存這些數據。這塊的修改點在dbgp\server.py文件中class property的initWithNode函數
? ? ? ? 還要新增一個轉換成Json的函數
父子(孫)進程管理
? ? ? ? 在我初步的設想中,我們只要讓調試器的Python代碼在一個進程中執行,然后以其為父進程,啟動一個執行Pydbgp庫的python子進程進程。這樣兩個進程之間關系比較簡單且易于維護。當我們需要關閉調試時,只要把子進程關閉即可。但是實際實現這段邏輯時,發現Windows上可以做到,但是在我的linux環境則不可以。于是只能靠孫子進程來完成這樣的設計。這塊代碼在class pydbgpd_stub中
def start(self):if (self._exc_cmd == None):raise NameError("exc_cmd is none")if "Windows" == platform.system():self._process = subprocess.Popen(self._exc_cmd, shell = False)else:self._process = subprocess.Popen(self._exc_cmd, shell = True, preexec_fn = os.setpgrp)time.sleep(2)self._cmd_client.Start()def stop(self):self._cmd_client.Stop()if not self._process:raise NameError("subprocess is none")else:if "Windows" != platform.system():pid = self._process.pidpgid = os.getpgid(pid)os.kill(-pgid, 9)self._process.terminate()self._process.kill()self._process = None
? ? ? ? 至此,我們便將該調試器相關設計和使用方法講完了。代碼維護在https://github.com/f304646673/PhpDebugger.git上。
總結
以上是生活随笔為你收集整理的跨平台PHP调试器设计及使用方法——拾遗的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 跨平台PHP调试器设计及使用方法——使用
- 下一篇: Simple Dynamic Strin