[程式] UE4 除錯技巧分享 (二)

作者: yekdniw (yekdniw)   2020-08-26 10:56:26
網頁版
https://yekdniwue.blogspot.com/2020/07/ue4CodeTrace2.html
上一篇介紹了兩個除錯小技巧
這一篇會比較著重在中斷點的部份。
技巧分享
這篇的複雜度稍高,而且技巧互相有關連,建議按照順序看。
環境
先稍微介紹一下本篇的環境。
開發平台的部分是Windows + Visual Studio 2017;
引擎是UE4.23。
照理說引擎版本沒什麼差,不過為了保險還是提一下。
從BP中斷點找出完整的Call Stack
在使用編輯器的時候,我們常常會使用BP中斷點來追查執行順序,
如同C++的中斷點一樣。
但是如果這個事件是從C++來的話,
在BlueprintDebugger是無法得知的,
如下圖所示。
[圖]
其實我們是有辦法知道的,在BP中斷點停住的狀態下,
回去Visual Studio,假設UE4 process已經是attach的狀態,
按Debug->BreakAll (如圖),這時候就可以看到Call Stack了。
[圖]
通常CallStack分頁會看到一大串,好像很嚇人,
這時候不要慌不要著急,看到Slate那種的都略過一直往下捲。
慢慢的看你應該會找到認識的函式,如下圖所示。
[圖]
以這張圖為例子,就可以知道BP的Tick是
從C++的Actor::Tick內呼叫ReceiveTick來的。
有時候你會看到CallStack極短,長的像這樣:
[圖]
這是因為斷點剛好停在別的Thread,這時候只要去Visual Studio的
Debug->Windows->Threads將Thread分頁叫出,
然後跳到MainThread就可以了。
從C++中斷點找出BP呼叫的來源
前一個技巧是BP斷點想知道從哪個C++呼叫進來的。
這個技巧是要說明如何從C++斷點找出是哪個BP呼叫來的。
這個技巧特別重要,因為就算是Packaged Game也可以抓出BP來源。
假設我現在中斷點停在C++的程式碼,有兩種方法可以試
方法1.
到Visual Studio的Watch視窗內輸入
{,,UE4Editor-Core}::PrintScriptCallstack()
或是
::PrintScriptCallstack()
然後去Visual Studio的Output視窗
就會看到印出來的BP Call Stack。
印象中前者是在Editor的時候用;
後者則是在Packaged Game用。
註1. 4.25後似乎不能用或是改了?不是很確定。
方法2.
或是在Watch視窗內分別輸入以下兩行
Stack.Node->OuterPrivate
Stack.Node->NamePrivate
然後去CallStack內找到UFunction::Invoke的函式,點兩下跳過去。
在Watch視窗就會看到OuterPrivate顯示哪一個Actor,
NamePrivate顯示哪一個函式,如下圖所示:
[圖]
有時候NamePrivate會顯示"ExecuteUbergraph_xxx",
這個意思是他是從BP的Event Graph來的。
屆時只好回去那個BP的EventGraph找呼叫點,
反正C++的函式名稱已知,到該BP搜尋函式名稱就會有答案。
接下來要介紹比較少用但是很強大的兩種中斷點。
使用Condition Breakpoint關注特定對象事件
首先先做情境的假設,假設場景上某個Actor class的實體很多,
但是問題卻只出在某一個特定的Actor,這個class有兩個函式A與B。
A函式內會有條件的呼叫B,而這個特定的Actor看起來就是條件沒過,
而且條件很複雜難以直接看出沒過的原因。
這時候該如何找出問題?
如果在C++的A函式內下斷點,每個Actor呼叫到A的時候都會停下來。
如果Actor很多或是A函式呼叫頻繁,是沒有辦法篩選出壞掉對象的。
我的答案是,用編輯器內
Blueprint的Debug Filter + condition breakpoint
步驟如下:
1. 先打開有問題的Actor class BP,假設是BP_CodeTraceActor。
2. 找出知道會壞掉的Actor,假設是BP_CodeTraceActorFoo。
3. 在BP編輯器內把Debug Filter選擇為BP_CodeTraceActorFoo。
4. 在BeginPlay或是Tick下BP的中斷點。如下圖
[圖]
Use debug filter and place BP breakpoint.
5. 執行遊戲。
6. 搭配前面的例子,在進入中斷點的狀態,
找出actor的記憶體位置並複製。
[圖]
Use this to fetch particular actor address.
7. 到函式A下condition breakpoint,設定為this = [記憶體位置]。
[圖]
Example of placing condition breakpoint.
8. 恢復執行,並等待A函式呼叫的時機觸發。
9. 進入中斷點的時候就是我們要的時機。
這個方法利用BP提供的Debug Filter加上Condition breakpoint,
可以協助我們有效率地找到問題發生的時機。
使用Data Breakpoint找出變數修改的時機
在某些少數的情況下,我們會找不到某個變數被修改的時機。
這時候就只好用Data Breakpoint來解決這個問題了。
這邊先提我遇過哪些情況:
1. 從Memory copy連續的記憶體位置,值就可能受到變動。
2. Setter的地方太多,無法每個地方都下中斷點。
3. 從Level Sequence直接設值是無法被搜尋到的。
使用Data Breakpoint的條件
使用這個方法有一個重要的條件,變數要定義在C++。
因為BP創建的變數被封裝成連續的記憶體,
所以很難找到方法找到BP變數的記憶體位置。
所以如果遇到這類的問題,建議先把變數搬進C++。
或是一開始就把變數定義在C++。
方法介紹
1. 一開始我們可以使用前一個技巧,先找出目標對象。
2. 在Watch視窗叫出變數,在變數前面加上&來獲得變數的記憶體位置。
如圖所示。
[圖]
3. 到Breakpoints分頁,選擇New->Data Breakpoint...
[圖]
4. 將記憶體位置填入Address欄位,選擇OK。
5. 繼續執行並想辦法觸發變數變更。
6. 斷點停下來的時候使用前面的技巧找出呼叫的來源。
在執行期間改變變數值做測試
在Visual Studio的Watch視窗是可以允許執行期間改值的。
有的時候包一個版本需要比較多的時間,
這時候就可以透過Watch在執行期間改值。
先模擬如果值是正確的,後續還有沒有其他問題,
藉此減少Iteration的次數。
舉例來說,某個函式A裡面有某個integer變數值應該要是5,
結果因為值是6造成問題。
這時候我們要回去程式碼修改,確保值不會是6。然後重包一版測試。
結果一測發現後面有另外一個值也是錯的,
這樣一來一回就要包兩個版本以上才能解決問題。
如果我們直接在Watch就把值改成6,然後繼續執行,
很快就會知道後面還有一個地方要修正。
這時候一併修正就可以省掉一次包版的時間。
結論
除錯技巧分享的部份差不多就到這邊。
我想我會的項目都在這兩篇裡面了,提供給大家參考。
作者: wangm4a1 (水兵)   2020-08-26 11:13:00
作者: metallican (鋼鐵人)   2020-08-26 11:59:00
作者: damody (天亮damody)   2020-08-26 15:47:00
作者: PathosCross (木偶君)   2020-08-26 22:23:00
作者: coolrobin (泳圈)   2020-08-27 00:12:00
作者: a82611141   2020-08-28 20:53:00

Links booklink

Contact Us: admin [ a t ] ucptt.com