[程式] 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