看板 GameDesign 關於我們 聯絡資訊
網頁版 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,然後繼續執行, 很快就會知道後面還有一個地方要修正。 這時候一併修正就可以省掉一次包版的時間。 結論 除錯技巧分享的部份差不多就到這邊。 我想我會的項目都在這兩篇裡面了,提供給大家參考。 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 59.120.146.90 (臺灣) ※ 文章網址: https://www.ptt.cc/bbs/GameDesign/M.1598410588.A.22B.html
wangm4a1: 推 08/26 11:13
metallican: 推 08/26 11:59
damody: 讚 08/26 15:47
PathosCross: 推 08/26 22:23
coolrobin: 推 08/27 00:12
a82611141: 推 08/28 20:53