diff --git a/onnxruntime/core/platform/windows/dll_load_error.cc b/onnxruntime/core/platform/windows/dll_load_error.cc new file mode 100644 index 0000000000000..94471e76ffd71 --- /dev/null +++ b/onnxruntime/core/platform/windows/dll_load_error.cc @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include +#include +#pragma comment(lib, "dbghelp.lib") +#include +#include +#include +#include "dll_load_error.h" + +struct HMODULE_Deleter { + typedef HMODULE pointer; + void operator()(HMODULE h) { FreeLibrary(h); } +}; + +using ModulePtr = std::unique_ptr; + +// If a DLL fails to load, this will try loading the DLL and then its dependencies recursively +// until it finds a missing file, then will report which file is missing and what the dependency +// chain is. +std::wstring DetermineLoadLibraryError(const wchar_t* filename_in, DWORD flags) { + std::wstring error(L"Error loading"); + + std::wstring filename{filename_in}; + while (filename.size()) { + error += std::wstring(L" \"") + filename + L"\""; + + // We use DONT_RESOLVE_DLL_REFERENCES instead of LOAD_LIBRARY_AS_DATAFILE because the latter will not process the import table + // and will result in the IMAGE_IMPORT_DESCRIPTOR table names being uninitialized. + ModulePtr hModule = ModulePtr{LoadLibraryExW(filename.c_str(), NULL, flags | DONT_RESOLVE_DLL_REFERENCES)}; + if (!hModule) { + error += L" which is missing."; + return error; + } + + // Get the address of the Import Directory + ULONG size{}; + IMAGE_IMPORT_DESCRIPTOR* import_desc = reinterpret_cast(ImageDirectoryEntryToData(hModule.get(), TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &size)); + if (!import_desc) { + error += L" No import directory found."; // This is unexpected, and I'm not sure how it could happen but we handle it just in case. + return error; + } + + // Iterate through the import descriptors to see which dependent DLL can't load + filename.clear(); + flags = 0; // Dependent libraries are relative, and flags like LOAD_WITH_ALTERED_SEARCH_PATH is undefined for those. + for (; import_desc->Characteristics; import_desc++) { + const char* dll_name = reinterpret_cast(reinterpret_cast(hModule.get()) + import_desc->Name); + // Try to load the dependent DLL, and if it fails, we loop again with this as the DLL and we'll be one step closer to the missing file. + ModulePtr hDepModule{LoadLibrary(dll_name)}; + if (!hDepModule) { + filename = std::wstring(dll_name, dll_name + strlen(dll_name)); + error += L" which depends on"; + break; + } + } + } + error += L" But no dependency issue could be determined."; + return error; +} diff --git a/onnxruntime/core/platform/windows/dll_load_error.h b/onnxruntime/core/platform/windows/dll_load_error.h new file mode 100644 index 0000000000000..019adfd9e16e4 --- /dev/null +++ b/onnxruntime/core/platform/windows/dll_load_error.h @@ -0,0 +1 @@ +std::wstring DetermineLoadLibraryError(const wchar_t* filename, DWORD flags); diff --git a/onnxruntime/core/platform/windows/env.cc b/onnxruntime/core/platform/windows/env.cc index 4fccad6dfeb37..9fdd323b365d6 100644 --- a/onnxruntime/core/platform/windows/env.cc +++ b/onnxruntime/core/platform/windows/env.cc @@ -39,6 +39,7 @@ limitations under the License. #include #include "core/platform/path_lib.h" // for LoopDir() +#include "core/platform/windows/dll_load_error.h" EXTERN_C IMAGE_DOS_HEADER __ImageBase; @@ -704,17 +705,18 @@ Status WindowsEnv::LoadDynamicLibrary(const PathString& wlibrary_filename, bool static constexpr DWORD bufferLength = 64 * 1024; std::wstring s(bufferLength, '\0'); FormatMessageW( - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)s.data(), - 0, NULL); + bufferLength, NULL); + s.erase(std::remove(s.begin(), s.end(), L'\r'), s.end()); + s.erase(std::remove(s.begin(), s.end(), L'\n'), s.end()); std::wostringstream oss; - oss << L"LoadLibrary failed with error " << error_code << L" \"" << s.c_str() << L"\" when trying to load \"" << wlibrary_filename << L"\""; + oss << DetermineLoadLibraryError(wlibrary_filename.c_str(), LOAD_WITH_ALTERED_SEARCH_PATH) + << L" (Error " << error_code << ": \"" << s.c_str() << "\")"; std::wstring errmsg = oss.str(); - // TODO: trim the ending '\r' and/or '\n' common::Status status(common::ONNXRUNTIME, common::FAIL, ToUTF8String(errmsg)); return status; }