1. 程式人生 > 其它 >自底向上程式碼除錯技巧

自底向上程式碼除錯技巧

原創:打碼日記(微信公眾號ID:codelogs),歡迎分享,轉載請保留出處。

簡介

我們常使用IDE的除錯功能解決程式問題,但很多同學用的是自上而下除錯法,即找到一個程式碼入口,打上斷點然後單步除錯。
但一些特殊的除錯場景,比如除錯框架程式碼,在不太熟悉框架程式碼的情況,會因為不知道從哪個入口開始除錯而感到迷茫,這時可以試試本文介紹的方法。

例子

比如,我們寫了一個介面,在獲取引數的時候亂碼瞭如下:

如何快速定位這個問題呢?

眾所周知,亂碼基本都是因為請求方與服務方字符集配置不一致產生的,比如請求方使用UTF-8,服務方使用GBK。
另外,既然上述介面中引數定義的是String型別,那麼spring或tomcat框架中一定有個地方將網路請求中的位元組流資料轉換成這個String物件。
那麼就可以這樣,我們在String類的所有構造方法中加入條件斷點,加斷點後在斷點處點滑鼠右鍵即可新增條件,比如new String().contains("abcdefg")

,然後請求這個介面時使用一個特殊的引數值abcdefg,這樣當框架中new出包含abcdefg的String物件時,我們的條件斷點就會命中,如下:

在String中新增條件斷點後,我們重啟應用,發如下請求來觸發斷點:

curl http://192.168.0.103:8080/test?data=abcdefg  

命中了條件斷點,如下:

我們往呼叫棧上面看,發現是handleQueryParameters中建立字串時使用的GBK來new出String,而GBK來自queryStringCharset屬性,如下:

那是哪裡配置成了GBK呢?我們再在設定queryStringCharset屬性的地方,即setQueryStringCharset

方法加入斷點,重啟應用並重新發送請求(重啟是為避免快取導致斷點觸發不了),如下:

我們再往呼叫棧上面看,發現setQueryStringCharset的引數來自connector.getURICharset(),如下:

同理,我們在賦值connector.setURICharset的地方打上斷點,重啟應用,發現在重啟的過程中就命中了斷點,如下:

我們再往呼叫棧上面看,發現connector.setURICharset的引數來自this.getUriEncoding()方法,而this是TomcatServletWebServerFactory型別

同理,我們在TomcatServletWebServerFactory.setUriEncoding

方法加上斷點,重啟應用,如下:

我們再往呼叫棧上面看,在to(Consumer<T> consumer)方法中發現uriEncoding來自this.supplier.get(),而this.supplier.get()取的應該就是ServerProperties$Tomcat物件中的urlEncoding屬性

同理,我們在它的setter方法ServerProperties$Tomcat.setUriEncoding裡面加上斷點,重啟應用,如下:

我們再往呼叫棧上面看,發現uriEncoding是從JavaBeanBinder.bind方法設定進來,從呼叫方法命名上不難看出,這個方法應該就是將配置檔案中的值set到配置類屬性中去的,當前被配置的類是ServerProperties$Tomcat,而正在配置的屬性是uri-encoding,如下:

我們在此處瞄下各變數與引數的組成,很快就在BeanPropertyBinder引數中找到,uriEncoding來自application-web.yml檔案的第4行第19列,如下:

果真,在application-web.yml中第4行,配置了uri-encoding: GBK,如下:

在將其改成UTF-8之後,發現亂碼就不存在了,如下:

ok,通過這個例子,相信你已經體會到自底向上除錯方法的訣竅了。

總結

上面這個例子其實可以有更快處理方法,比如在當前工程全文搜尋GBK,又或者google一下springboot亂碼之類的關鍵詞,也能很快解決問題,但思路是很重要的,比如類似下面的場景,也能運用這裡的方法:

  1. 系統執行時,不知道什麼地方的程式碼老是執行了一條刪除的sql,我們可以和上面一樣,在String裡面打上條件斷點,條件是包含刪除sql的部分片段。
  2. 系統啟動時,不知道什麼地方的程式碼監聽了12345埠,我們可以在ServerSocket裡面加上條件斷點,條件是port==12345
  3. 系統執行時,不知道什麼地方的程式碼老是寫/tmp/app.log檔案,我們可以在FileOutputStream.write方法加上條件斷點,條件是File=="/tmp/app.log"

往期內容

真正理解可重複讀事務隔離級別
Linux文字命令技巧(下)
Linux文字命令技巧(上)
原來awk真是神器啊
常用網路命令總結