twitterでSanitizerの情報貰ったので簡単に纏めました。
メモリが壊れてるっぽい挙動してる!どう調べよう!!
という事があると思います。
(UE4だとBPのみプロジェクトの場合ではあんまり発生しないかと思います。C++を利用するプロジェクトの方は有ると思います)
自前プログラムでしたらmallocをhookしてあれこれ自作処理する事が多々ありますが、UE4程規模が大きくなるとそれも大変です。
そこでメモリ破壊を検知出来るAddress Sanitizer(アドレスサニタイザー)を使って調べます
※使えるのはMac / Linux / 一部コンソールのみになります。
そのままだとWindows/Androidは使えません。多分。
また、エンジンをリビルドする必要あります。多分。
UE5からWindows版でもAddress Sanitizerが使用できるようになりました。
→UE5+WindowsでのAddress Sanitizer
UE4.22.3を使用して検証
Address Sanitizerとは
メモリ破壊、バッファオーバーランや多重開放等を検知してログ出力、停止してくれる機能です。
- 参考
- 【C++】MSVCでAddressSanitizerを使ってみる - logicalbeat
- clang の AddressSanitizer を使って、バッファオーバーフロー/ヒープオーバーフローを検出する - Qiita
- Address Sanitizerでメモリ不正アクセスを検知する - Qiita
- AddressSanitizer - Wikipedia
有効化する
Engine/Source/Program/UnrealBuildTool/Platform/<プラットフォーム名>/UEBuild<プラットフォーム名>.cs
の bEnableAddressSanitizer 変数を見てください。
-
[XmlConfigFile(Category = "BuildConfiguration")...] と書かれている場合は BuildConfiguration で設定できると思います
- 参考
- ビルド コンフィギュレーション - 公式ドキュメント
https://docs.unrealengine.com/4.27/ja/ProductionPipelines/BuildTools/UnrealBuildTool/BuildConfiguration/ - UE4 UnrealBuildTool の設定 BuildConfiguration.xml - ホイール欲しい ハンドル欲しい
https://wlog.flatlib.jp/item/1876
- ビルド コンフィギュレーション - 公式ドキュメント
- 多分下記のような感じでいけると思います
- 参考
<?xml version="1.0" encoding="utf-8"?>
<Configuration xmlns="https://www.unrealengine.com/BuildConfiguration">
<BuildConfiguration>
<bEnableAddressSanitizer>true</bEnableAddressSanitizer>
</BuildConfiguration>
</Configuration>
-
[ConfigFile(ConfigHierarchyType.Engine, <パラメータータグ>]と書かれている場合は Config で設定出来ると思います
- Engine/Config/<プラットフォーム名>/<プラットフォーム名>Engine.ini
[<パラメータータグ>]
bEnableAddressSanitizer=true
- 上手く行かない場合
-
Source/Program/UnrealBuildTool/Platform/<プラットフォーム名>/UEBuild<プラットフォーム名>.cs
- bEnableAddressSanitizer を直接 true に書き換えましょう
-
Source/Program/UnrealBuildTool/Platform/<プラットフォーム名>/UEBuild<プラットフォーム名>.cs
それぞれ設定した後はエンジン含めてプロジェクトをリビルドしてください
あと
後はそのまま実行すれば検知してログ出力、デバッガで停止してくれる筈です。
検証
ざっくりと要素範囲外にwriteする関数を用意します
struct FTest
{
int32 TestVal[10] = {0};
};
UCLASS()
class UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
static void TestASAN(int32 Index, int32 Value);
static FTest TestVal;
};
void UMyBlueprintFunctionLibrary::TestASAN(int32 Index, int32 Value)
{
TestVal.TestVal[Index] = Value;
}
これを、下記で呼び出してみます。
TestASAN(10, 3)
配列要素外に書き込みしているのでNGです。
デバッガ
Visual Studioでデバッガを繋いでいると、例外がスローされてキャッチできます。
出力に表示されるエラー内容
UE5 + windows版で確認。0x7ff7c81e63d0 is located 0 bytes to the right of global variable 'UMyBlueprintFunctionLibrary::TestVal' defined in 'MyBlueprintFunctionLibrary.cpp:7:35' (0x7ff7c81e6380) of size 80
SUMMARY: AddressSanitizer: global-buffer-overflow D:\u\Test\TestASAN5\Source\TestASAN5\Private\MyBlueprintFunctionLibrary.cpp:11 in UMyBlueprintFunctionLibrary::TestASAN
Shadow bytes around the buggy address:
0x11f74c63cc20: 00 00 00 00 00 00 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9
0x11f74c63cc30: 01 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9
0x11f74c63cc40: 00 00 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9
0x11f74c63cc50: 01 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9
0x11f74c63cc60: 01 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9
=>0x11f74c63cc70: 00 00 00 00 00 00 00 00 00 00[f9]f9 f9 f9 f9 f9
0x11f74c63cc80: f9 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9
0x11f74c63cc90: f9 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9
0x11f74c63cca0: f9 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9
0x11f74c63ccb0: f9 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9
0x11f74c63ccc0: f9 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
=================================================================
==12328==ERROR: AddressSanitizer: global-buffer-overflow on address 0x7ff7c81e63d0 at pc 0x7ff7b0d25542 bp 0x006729374e50 sp 0x006729374e58
WRITE of size 4 at 0x7ff7c81e63d0 thread T0
#0 0x7ff7b0d25541 in UMyBlueprintFunctionLibrary::TestASAN D:\u\Test\TestASAN5\Source\TestASAN5\Private\MyBlueprintFunctionLibrary.cpp:11
#1 0x7ff7b0d26abf in UMyBlueprintFunctionLibrary::execTestASAN D:\u\Test\TestASAN5\Intermediate\Build\Win64\TestASAN5\Inc\TestASAN5\MyBlueprintFunctionLibrary.gen.cpp:23
#2 0x7ff7b2622a66 in UObject::execCallMathFunction C:\u\ue5\Engine\UnrealEngine\Engine\Source\Runtime\CoreUObject\Private\UObject\ScriptCore.cpp:945
#3 0x7ff7b25da1d7 in ProcessLocalScriptFunction C:\u\ue5\Engine\UnrealEngine\Engine\Source\Runtime\CoreUObject\Private\UObject\ScriptCore.cpp:1109
#4 0x7ff7b25d8464 in UObject::ProcessInternal C:\u\ue5\Engine\UnrealEngine\Engine\Source\Runtime\CoreUObject\Private\UObject\ScriptCore.cpp:1170
#5 0x7ff7b20f6f7f in UFunction::Invoke C:\u\ue5\Engine\UnrealEngine\Engine\Source\Runtime\CoreUObject\Private\UObject\Class.cpp:5912
#6 0x7ff7b25d6fcf in UObject::ProcessEvent C:\u\ue5\Engine\UnrealEngine\Engine\Source\Runtime\CoreUObject\Private\UObject\ScriptCore.cpp:2001
#7 0x7ff7bb3ef087 in AActor::ProcessEvent C:\u\ue5\Engine\UnrealEngine\Engine\Source\Runtime\Engine\Private\Actor.cpp:1030
#8 0x7ff7bc656812 in FLatentActionManager::TickLatentActionForObject C:\u\ue5\Engine\UnrealEngine\Engine\Source\Runtime\Engine\Private\LatentActionManager.cpp:318
#9 0x7ff7bc630bf7 in FLatentActionManager::ProcessLatentActions C:\u\ue5\Engine\UnrealEngine\Engine\Source\Runtime\Engine\Private\LatentActionManager.cpp:225
#10 0x7ff7bc6e3d1f in UWorld::Tick C:\u\ue5\Engine\UnrealEngine\Engine\Source\Runtime\Engine\Private\LevelTick.cpp:1576
#11 0x7ff7bc28bf1a in UGameEngine::Tick C:\u\ue5\Engine\UnrealEngine\Engine\Source\Runtime\Engine\Private\GameEngine.cpp:1828
#12 0x7ff7adeef941 in FEngineLoop::Tick C:\u\ue5\Engine\UnrealEngine\Engine\Source\Runtime\Launch\Private\LaunchEngineLoop.cpp:5209
#13 0x7ff7adf3eb7b in GuardedMain C:\u\ue5\Engine\UnrealEngine\Engine\Source\Runtime\Launch\Private\Launch.cpp:185
#14 0x7ff7adf3ec79 in GuardedMainWrapper C:\u\ue5\Engine\UnrealEngine\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:147
#15 0x7ff7adf43de5 in LaunchWindowsStartup C:\u\ue5\Engine\UnrealEngine\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:283
#16 0x7ff7adf645af in WinMain C:\u\ue5\Engine\UnrealEngine\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:330
#17 0x7ff7bf8ed319 in __scrt_common_main_seh d:\a01\_work\43\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
#18 0x7ffe2c887033 in BaseThreadInitThunk+0x13 (C:\WINDOWS\System32\KERNEL32.DLL+0x180017033)
#19 0x7ffe2d502650 in RtlUserThreadStart+0x20 (C:\WINDOWS\SYSTEM32\ntdll.dll+0x180052650)
UE5+WindowsでのAddress Sanitizer
Visual Studio 2019 16.7 以降で使用できます。
成功していると、下記にdllが追加されているかと思います。
パッケージ/<プロジェクト名>/Binaries/Win64/clang_rt.asan_dynamic-x86_64.dll
(これで起動すると 0xC0000005 の例外スローが大量に出たのでキャッチしないようにしないと動かせない?)
- 対応はこの辺りのcommitかと思われます。
https://github.com/EpicGames/UnrealEngine/commit/9a3fb45cb4fbdba1b3f363b68e25807bc638b90e
(4.27にもパッチ出来そう。途中まで試したけどまだ未確認)