在 VIsual Studio 使用 Just My Code,避免 C++ 偵錯時顯示大量外部函式庫的內容

| | 0 Comments| 09:59
Categories:

在使用 Visual C++ 開發程式的時候,其實有許多好用的偵錯工具,都可以幫助開發者找到程式的問題到底在哪;包括了各種中斷點、呼叫堆疊(call stack)、以及逐步執行(step)等等,能善用的話,都是很方便的工具。

但是,當外部函式庫使用多了之後,其實在使用呼叫堆疊和逐步執行的時候,往往會碰到一個問題,那就是外部函式庫會造成嚴重的干擾,讓偵錯變得相當麻煩…

比如說下面就是 Heresy 自己用 Boost Beast 來試著寫 WebSocket Server 時,中斷時的呼叫堆疊:

可以看到,呼叫堆疊裡面,基本上都是滿滿的 boost::asio 的東西。

但是在大部分的狀況,其實開發者根本不會想要進去看 boost::asio 內部到底怎麼呼叫的(當然有的時候還是必要的),所以這些大量的呼叫堆疊,反而讓使用者真正需要、自己撰寫的程式變得更難找了…

而逐步執行的時候,其實更為明顯。有的時候,看來簡單的一個函式呼叫,真的要逐步執行進去,還得先「通過」大量的標準函式庫、或是外部函式庫,才能進到自己想要的函式裡面。

這些外部函式庫的干擾,其實很多時候也都大幅地干擾偵錯的工作。


而微軟在 Visual Studio 提供的「Just My Code」的功能(官網,以下簡稱 JMC)(應該是 VisualStudio 2013 就有了),就是一個允許開發者排除這些外部函式庫的干擾,只專心在自己撰寫的程式碼、進行偵錯的工具!

這項功能在 Visual Studio 預設就是開啟的,就算不做特殊的設定,也會有基本的功能。如果被被判斷成「非使用者程式碼」(non-user code)的部分、隱藏起來;像是上面的截圖中,就可以看到底下有一個灰色的「外部程式碼」,這部分就是被 JMC 過濾掉的了。

但是,由於在開發 C++ 程式的時候,常常會用到許多函式庫,Visual Studio 也不太可能知道到底哪些函式庫是使用者真正在開發的,所以就需要自己去設定了~(尤其是 header-only 的函式庫)

而 Visual Studio 在設定 C++ 的 JMC 的時候,分成兩個部分。一個是「呼叫堆疊」,另一個是「逐步執行」,兩者個設定是完全獨立的。


呼叫堆疊

在呼叫堆疊的部分,這邊是需要自己撰寫一份副檔名為「.natjmc」的檔案,其內容是 xml 的格式,然後放在特定的資料夾內。

下面就是官方給的範例:

<?xml version="1.0" encoding="utf-8"?>
<NonUserCode xmlns="http://schemas.microsoft.com/vstudio/debugger/jmc/2015">
  <!-- Modules -->
  <Module Name="ModuleSpec" />
  <Module Name="ModuleSpec" Company="CompanyName" />
  <!-- Files -->
  <File Name="FileSpec"/>
  <!-- Functions -->
  <Function Name="FunctionSpec" />
  <Function Name="FunctionSpec" Module ="ModuleSpec" />
  <Function Name="FunctionSpec" Module ="ModuleSpec" ExceptionImplementation="true" />
</NonUserCode>

在 .natjmc 這邊,它提供了三種不同的設定方法,分別是「Module」(模組)、「File」(檔案)和「Function」(函式)。

Module:DLL 模組

其中,「Module」的部分,一定要給的屬性是「Name」,他的值會是路徑的形式,也支援萬用字元(* 和 ?)。比如說

<Module Name="?:3rdPartyUtilLibs*" />

就代表所有磁碟機上的「3rdPartyUtilLibs」的所有內容。

而「Company」的部分,則算是用來寫註解的,可以記錄這些模組是哪個公司的。

而如果參考官方「default.natjmc」的內容,則可以發現,「Module」所列舉的都是 DLL 檔的部分;例如:

<!-- Windows-->
<Module Name="?:WindowsSystem32*.dll" Company="Microsoft Corporation" />
<Module Name="?:WindowsSysWOW64*.dll" Company="Microsoft Corporation" />
<Module Name="?:WindowsWinSxs*" Company="Microsoft Corporation" />

File:個別檔案

「File」的部分,則大多數就是對應到 header 檔了~他的「Name」屬性基本上就和「Module」的一樣,是給一個路徑。

下面就是官方的設定:

<!-- Standard C++ header files-->
<File Name="*VCinclude*" />
<File Name="*VCatlmfc*" />
<!-- Generated files-->
<File Name="*.g.h" />
<File Name="*.g.cpp" />
<File Name="*.g.hpp" />

可以看到,設定的彈性還算滿高的,可以針對路徑設定,也可以針對檔名來設定。

Function:個別函式、或是 namespace 等

「Function」的部分,則是可以設定把某些特定的函式當成非使用者的程式。

而在設定上,他算是有更多的屬性可以使用。

除了「name」可以指定函式的名稱(應該也可以用萬用字元)外,也能指定是哪個「module」(DLL 檔);另外,也還有一個「ExceptionImplementation」,可以設定當有例外發生的時候,顯示丟出例外的函式(老實說,不太懂)。

下面就是官方的設定:

<!-- CRT Exception implementation-->
<Function Name="__abi_ThrowIfFailed" ExceptionImplementation="true" />
<Function Name="__abi_WinRTraiseAccessDeniedException" ExceptionImplementation="true" />
<Function Name="__abi_WinRTraiseException" ExceptionImplementation="true" />
<Function Name="_CxxThrowException" ExceptionImplementation="true" />
<Function Name="RaiseException" Module ="kernelbase.dll" ExceptionImplementation="true" />
<Function Name="RaiseException" Module ="kernel32.dll" ExceptionImplementation="true" />

而 Heresy 自己測試,由於他是去比對函式完整的名稱,再加上應該也有支援萬用字元,所以其實也是可以靠 namespace、或是 class 名稱,來做過濾的~

例如寫成:

<Function Name="boost::*" ExceptionImplementation="true" />

的話,就可以把所有 Boost 的東西都當成非使用者的程式碼了!


逐步執行

在逐步執行的部分(F11),他一樣是要去寫一個 xml 檔,不過副檔名是「.natstepfilter」,寫完後一樣要放到特定的資料夾裡。

下面是官方給的範例:

<?xml version="1.0" encoding="utf-8"?>
<StepFilter xmlns="http://schemas.microsoft.com/vstudio/debugger/natstepfilter/2010">
  <Function>
    <Name>FunctionSpec</Name>
    <Action>StepAction</Action>
  </Function>
  <Function>
    <Name>FunctionSpec</Name>
    <Module>ModuleSpec</Module>
    <Action>StepAction</Action>
  </Function>
</StepFilter>

他的過濾方法基本上只有「Function」(函式)一種,所以看起來相對簡單。

而在語法設計上,「逐步執行」的「.natstepfilter」和「呼叫堆疊」的「.natjmc」明顯的差別,就是它不是使用屬性、而是使用元素來指定 Function 的內容。

他總共支援三個元素:「Name」、「Module」、「Action」;其中「Module」不一定要給。

其中,「Name」就是函式的名稱,根據文件的說法,他是「指定要比對的完整函式名稱之 ECMA-262 格式化規則運算式」,也可以使用萬用字元。

「Module」則是指定這個函式在哪個模組,裡面給的是路徑;但是不一定要指定。

「Action」則有「NoStepInto」和「StepInto」兩種值,前者就是不要逐步執行進去;後者則是會覆蓋前者、變成要執行進去。

像如果要跳過 std 和 boost 的所有函式的話,就可以寫成:

<?xml version="1.0" encoding="utf-8"?>
<StepFilter xmlns="http://schemas.microsoft.com/vstudio/debugger/natstepfilter/2010">
    <Function>
        <Name>std::.*</Name>
        <Action>NoStepInto</Action>
    </Function>
    <Function>
        <Name>boost::.*</Name>
        <Action>NoStepInto</Action>
    </Function>
</StepFilter>

這樣一來,在偵錯的時候,如果是透過逐步執行(F11),就不會再進到 std 和 boost 的函式裡了~


安裝設定檔

在撰寫好前面的「.natjmc」和「.natstepfilter」後,要讓他真的有用的話,還需要把檔案複製到特定的資料夾才行。

如果是希望這台電腦上所有的人都適用的話,那就是放在:

%VsInstallDirectory%Common7PackagesDebuggerVisualizers

這個資料夾。

在 Windows x64 的電腦上,應該會是:

C:Program Files (x86)Microsoft Visual Studio
14.0Common7PackagesDebuggerVisualizers

而如果只是個人要用的話,則是放在:

%USERPROFILE%My DocumentsVisual Studio 2017Visualizers

這個資料夾。

其中,「14.0」和「2017」這些數字會依據版本的不同而有所不同。

而這兩種檔案的修改,看來也不需要重新啟動 Visual Studio、只要重新開始偵錯就會生效了。


使用結果呢?如果是像本業最開始的呼叫堆疊,在把 boost 排除後,就會變成下面的樣子:

可以看到,變得相當簡潔了!

而如果還是想看到完整的呼叫堆疊的話,也可以在這個視窗上,點選滑鼠右鍵,把「顯示外部程式碼」勾起來就可以了。(下圖)

而在偵錯的時候,如果有正確按照上面的設定 .natstepfilter、把 std 和 boost 的話,應該也會發現,使用 F11 逐步執行的時候,所有 boost 和 std 的函式都不會走進去了~

下面就是官方提供的對比示意。首先是沒有 JMC 的狀況:

下面則是有 JMC 的狀況:

這樣在偵錯的時候,應該會方便很多!

而如果真的還是需要去看這些外部函式庫的內容的話,其實還是可以再停下來的時候,在程式碼的區域按下滑鼠右鍵選單,點選「逐步執行至特定處」,來選擇要直接跳到哪個函式。

如此一來,也可以更快速地找到自己需要的程式碼區段。


參考:《Announcing C++ Just My Code Stepping in Visual Studio

額外參考:讓 Visual C++ 2012 在偵錯的時候可以直接看到圖片的內容

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *