Files
BlueRoseNote/03-UnrealEngine/Gameplay/Debug/UE5编辑器开启ASAN.md

9.1 KiB
Raw Blame History

title, date, excerpt, tags, rating
title date excerpt tags rating
UE5编辑器开启ASAN 2026-05-04 22:45:02

UE5的编辑器直接开启ASAN会因为有全局变量初始化依赖问题导致误报阻塞。

虽然网上以及有人探究过底层原因以及解决方式:https://zhuanlan.zhihu.com/p/14576791261 但是此解法需要修改大量代码而且容易因为UE的升级导致有新代码需要修改非常不灵活。 后面公司的UE交流群里有同事列出更好的解决方式就是在引擎启动后主动调用DLL的ASAN初始化函数。 以下是此修改方式是具体落地修改:

1. 修改Core.Build.cs

Core.Build.cs添加DIA SDK依赖

// DIA SDK
if (Target.Platform == UnrealTargetPlatform.Win64)
{
	string DiaSdkDir = Target.WindowsPlatform.DiaSdkDir;
	if (DiaSdkDir == null)
	{
		throw new System.Exception("Unable to find DIA SDK directory");
	}

	string ToolChainDir = Target.WindowsPlatform.ToolChainDir;
	string ToolChainArch = (Target.Architecture == UnrealArch.Arm64) ? "arm64" : "x64";
	PrivateIncludePaths.Add(Path.Combine(ToolChainDir, "atlmfc", "include"));
	PublicAdditionalLibraries.Add(Path.Combine(ToolChainDir, "atlmfc", "lib", ToolChainArch, "atls.lib"));

	string DiaArch = (Target.Architecture == UnrealArch.Arm64) ? "arm64" : "amd64";
	PrivateIncludePaths.Add(Path.Combine(DiaSdkDir, "include"));
	PublicAdditionalLibraries.Add(Path.Combine(DiaSdkDir, "lib", DiaArch, "diaguids.lib"));
	RuntimeDependencies.Add("$(TargetOutputDir)/msdia140.dll", Path.Combine(DiaSdkDir, "bin", DiaArch, "msdia140.dll"));
}

2.新建下面文件到此路径里编译 Engine/Source/Runtime/Core/Private/Windows/命名随意如AsanInitializer.cpp

#include <Windows.h>
#include <tchar.h>
#include <psapi.h>
#include <dia2.h>
#include <debugapi.h>
#include <cassert>
#include <filesystem>
#include <unordered_set>
#include <vector>

// ============================================================================
// Debug Print Utility
// ============================================================================

template<typename ...TArgs, size_t size>
static void DebugPrint(const TCHAR(&format)[size], TArgs&&... args)
{
	TCHAR msg[1024] = {};
	int written = _stprintf_s(msg, format, std::forward<TArgs>(args)...);
	if (written <= 0)
		return;
	OutputDebugString(msg);
}

namespace fs = std::filesystem;

EXTERN_C IMAGE_DOS_HEADER __ImageBase;

// ============================================================================
// Symbol Resolution using DIA SDK
// ============================================================================

class SimpleDiaCallback : public IDiaLoadCallback2
{
	LONG m_refCount = 0;

public:
	HRESULT STDMETHODCALLTYPE QueryInterface(REFIID rid, void** ppUnk) override
	{
		if (!ppUnk) return E_INVALIDARG;

		if (rid == __uuidof(IDiaLoadCallback2) ||
			rid == __uuidof(IDiaLoadCallback) ||
			rid == __uuidof(IUnknown))
		{
			*ppUnk = this;
			AddRef();
			return S_OK;
		}
		*ppUnk = nullptr;
		return E_NOINTERFACE;
	}

	ULONG STDMETHODCALLTYPE AddRef() override { return ++m_refCount; }
	ULONG STDMETHODCALLTYPE Release() override
	{
		auto count = --m_refCount;
		if (count == 0) delete this;
		return count;
	}

	// 实现必要的回调方法
	HRESULT STDMETHODCALLTYPE NotifyDebugDir(BOOL, DWORD, BYTE*) override { return S_OK; }
	HRESULT STDMETHODCALLTYPE NotifyOpenDBG(LPCOLESTR, HRESULT) override { return S_OK; }
	HRESULT STDMETHODCALLTYPE NotifyOpenPDB(LPCOLESTR, HRESULT) override { return S_OK; }
	HRESULT STDMETHODCALLTYPE RestrictRegistryAccess() override { return S_OK; }
	HRESULT STDMETHODCALLTYPE RestrictSymbolServerAccess() override { return S_OK; }
	HRESULT STDMETHODCALLTYPE RestrictOriginalPathAccess() override { return S_OK; }
	HRESULT STDMETHODCALLTYPE RestrictReferencePathAccess() override { return S_OK; }
	HRESULT STDMETHODCALLTYPE RestrictDBGAccess() override { return S_OK; }
	HRESULT STDMETHODCALLTYPE RestrictSystemRootAccess() override { return S_OK; }
};

static DWORD GetSymbolOffset(HMODULE hModule, const wchar_t* name)
{
	wchar_t moduleName[MAX_PATH] = {};
	GetModuleFileNameW(hModule, moduleName, MAX_PATH);

	HRESULT hr = CoInitialize(nullptr);
	bool needUninitialize = SUCCEEDED(hr);

	IDiaDataSource* diaDataSource = nullptr;
	hr = CoCreateInstance(__uuidof(DiaSource), nullptr, CLSCTX_INPROC_SERVER,
		__uuidof(IDiaDataSource), (void**)&diaDataSource);
	if (FAILED(hr))
	{
		if (needUninitialize) CoUninitialize();
		return 0;
	}

	SimpleDiaCallback* callback = new SimpleDiaCallback();
	callback->AddRef();

	hr = diaDataSource->loadDataForExe(moduleName, nullptr, callback);
	if (FAILED(hr))
	{
		callback->Release();
		diaDataSource->Release();
		if (needUninitialize) CoUninitialize();
		return 0;
	}

	IDiaSession* session = nullptr;
	hr = diaDataSource->openSession(&session);
	if (FAILED(hr))
	{
		callback->Release();
		diaDataSource->Release();
		if (needUninitialize) CoUninitialize();
		return 0;
	}

	IDiaSymbol* globalSymbol = nullptr;
	hr = session->get_globalScope(&globalSymbol);

	DWORD result = 0;
	if (SUCCEEDED(hr))
	{
		IDiaEnumSymbols* enumSymbols = nullptr;
		if (SUCCEEDED(globalSymbol->findChildren(SymTagFunction, name, nsRegularExpression, &enumSymbols)))
		{
			IDiaSymbol* symbol = nullptr;
			ULONG count = 0;
			while (SUCCEEDED(enumSymbols->Next(1, &symbol, &count)) && count == 1)
			{
				wchar_t* symbolName = nullptr;
				if (SUCCEEDED(symbol->get_name(&symbolName)) && wcscmp(symbolName, name) == 0)
				{
					symbol->get_relativeVirtualAddress(&result);
				}
				symbol->Release();
			}
			enumSymbols->Release();
		}
		globalSymbol->Release();
	}

	session->Release();
	callback->Release();
	diaDataSource->Release();
	if (needUninitialize) CoUninitialize();

	return result;
}

static FARPROC MyGetSymbolAddress(HMODULE hModule, const wchar_t* name)
{
	DWORD offset = GetSymbolOffset(hModule, name);
	if (offset == 0)
	{
		return nullptr;
	}
	return (FARPROC)((char*)hModule + offset);
}

// ============================================================================
// ASAN Pre-Initialization
// ============================================================================

static int ExecInitializeClonedVariables(HMODULE hModule)
{
	using InitializeClonedVariablesFunc = int();
	auto InitializeClonedVariables = (InitializeClonedVariablesFunc*)MyGetSymbolAddress(hModule, L"__asan_initialize_cloned_variables");
	if (!InitializeClonedVariables)
	{
		return -1;
	}

	TCHAR moduleName[MAX_PATH] = {};
	if (GetModuleFileName(hModule, moduleName, MAX_PATH) > 0)
	{
		DebugPrint(_T("AsanPreInit: Call InitializeClonedVariables on %s\n"), moduleName);
	}

	return InitializeClonedVariables();
}

static std::vector<HMODULE> GetModules(HANDLE hProcess)
{
	size_t count = 2048;
	std::vector<HMODULE> modHandles;
	DWORD cbNeeded;
	size_t loadedCount = 0;
	do
	{
		modHandles.resize(count);
		size_t size = modHandles.size() * sizeof(HMODULE);
		assert(size < MAXDWORD);
		if (!EnumProcessModules(hProcess, modHandles.data(), (DWORD)size, &cbNeeded))
		{
			DebugPrint(_T("EnumProcessModules failed: %d\n"), GetLastError());
		}
		loadedCount = cbNeeded / sizeof(HMODULE);
		if (loadedCount < modHandles.size())
		{
			modHandles.resize(loadedCount);
			break;
		}
		count *= 2;
	} while (loadedCount <= 10000);
	return modHandles;
}

static std::unordered_set<std::string> GetMainBinaries()
{
	TCHAR executable[1024] = {};
	GetModuleFileName(nullptr, executable, sizeof(executable));
	fs::path execPath(executable);
	std::unordered_set<std::string> binaries;
	for (auto& entry : fs::directory_iterator(execPath.parent_path()))
	{
		auto filename = entry.path().filename();
		if (filename.extension() == ".dll")
		{
			binaries.emplace(filename.string());
		}
	}
	return binaries;
}

__declspec(dllexport) bool AsanPreInit_Setup()
{
	auto modules = GetModules(GetCurrentProcess());
	auto binaries = GetMainBinaries();
	HANDLE process = GetCurrentProcess();
	for (auto& mod : modules)
	{
		if (!mod)
			continue;
		if (mod == (HMODULE)&__ImageBase)
			continue;
		char baseName[MAX_PATH] = {};
		GetModuleBaseNameA(process, mod, baseName, sizeof(baseName));
		// 简单过滤下除当前执行文件目录外的动态库
		if (!binaries.count(baseName))
			continue;
		ExecInitializeClonedVariables(mod);
	}
	return true;
}

class AsanPreInit
{
public:
	AsanPreInit() { AsanPreInit_Setup(); }
};
static AsanPreInit g_Init;

3.正常运行

首次运行可能会有DIA报错按照下图方式修复

根本原因 系统注册表中找不到 DiaSource 的 CLSID,即msdia140.d11或对应版本没有被注册到 Windows COM 注册表中。

解决方案一注册msdia*.dll (需要管理员权限) 以管理员身份运行 CMD执行以下命令根据你的 VS 版本选择):

  1. VS2022
    1. regsvr32 "C:\Program Files\Microsoft Visual Studio\2022\Professional\DIA SDK\bin\amd64\msdia140.dll"
  2. VS2019
    1. regsvr32 "C:\Program Files (x86) \Microsoft Visual Studio\2019\Professional\DIA SDK\bin\amd64\msdia140.dll"
  3. VS2017
    1. regsvr32 "C:\Program Files (x86) \Microsoft Visual Studio\2017\Professional\DIA SDK\bin\amd64\msdia140.dll" 注册成功后再运行代码即可