天天看點

探索 dotnet core 為何在 Windows7 系統需要更新檔的原因

在一些 Windows 7 系統上,根據 dotnet 官方文檔,需要安裝上 KB2533623 更新檔,才能運作 dotnet core 或 .NET 5 等應用。盡管非所有的裝置都需要安裝此,但這也讓應用的分發不便,安裝包上都需要帶上更新檔給使用者安裝。此更新檔同時也要求安裝完成之後重新開機系統,這對使用者端來說,也是較不友善。本文來聊聊為什麼 dotnet core 一系的架構依賴于此更新檔

特别感謝 lsj 給我講解 Win32 調用部分的知識和幫我調查具體的原因,我隻是記錄的工具人

更新檔

開始之前,先來理一下所需更新檔的情況,不想看更新檔細節還請跳到下文主題這章。 準确來說,在目前 2021.12.25 官方推薦 Win7 系統打上的更新檔是 KB3063858 更新檔

KB3063858: MS15-063:Windows 核心中的漏洞可能允許特權提升:2015 年 6 月 9 日

此安全更新可解決 Windows 中的一個漏洞。如果使用者通路包含特殊設計的檔案的網絡共享(或指向網絡共享的網站)時,此漏洞可能允許特權提升。但是在所有情況下,攻擊者都無法強制使用者通路此類網絡共享或網站。

也有夥伴推薦給我的是安裝 KB4457144 更新檔。但是 KB4457144 更新檔太大了,包含太多内容,帶上這個更新檔沒什麼優勢

KB4457144: 2018 年 9 月 11 日 - KB4457144(月度彙總)

在 GitHub 上的 Security update KB2533623 no longer available · Issue #20459 · dotnet/docs 讨論上,有大佬表示 KB3063858 或 KB4457144 包含了 KB2533623 更新檔内容:

The thread claims that KB2533623 is superseded by KB3063858 or KB4457144. https://github.com/dotnet/docs/issues/20459#issuecomment-689513876

值得一說的是對需要安裝 KB3063858 更新檔的系統來說,大多數都需要額外加上證書,參閱 https://www.microsoft.com/pkiops/Docs/Repository.htm 是以我認為對于用戶端分發來說,打上 KB2533623 更新檔似乎更好。但是 KB2533623 目前被微軟下架了,請看 Security update KB2533623 no longer available · Issue #20459 · dotnet/docs

這是 KB2533623 的下載下傳位址: http://www.microsoft.com/download/details.aspx?familyid=c79c41b0-fbfb-4d61-b5d8-cadbe184b9fc

另外,在剛推送 dotnet core 3.0 的預覽版本時,有夥伴在 WPF 官方倉庫回報說需要加上 KB2999226 更新檔。此 KB2999226 更新檔是

Windows 中的 Universal C Runtime 更新

的内容,參閱 https://github.com/dotnet/wpf/issues/2009#issuecomment-543872453

也許可以使用 runtime.win7-x64.Microsoft.NETCore.Windows.ApiSets NuGet 庫 代替 KB2999226 更新檔内容,隻需要将

api-xxxxx.dll

這些檔案拷貝到輸出路徑即可。或者是解包 VC++ 2015 的分發包裡的檔案,将

api-xxxxx.dll

ucrtbase.dll

拷貝到輸出路徑即可

是以,對于用戶端分發來說,似乎采用 KB2533623 最小更新檔,然後在輸出路徑上拷貝好

api-xxxxx.dll

這些檔案到輸出路徑是最佳方法

下載下傳位址:

  • KB2533623 x86
    • MD5: EDF1D538C85F24EC0EF0991E6B27F0D7
    • SHA1: 25BECC0815F3E47B0BA2AE84480E75438C119859
  • KB2533623 x64
    • MD5: 0A894C59C245DAD32E6E48ECBDBC8921
    • SHA1: 8A59EA3C7378895791E6CDCA38CC2AD9E83BEBFF
  • KB3063858 32-bit
  • KB3063858 64-bit

主題

清理好了各個更新檔的關系之後,咱回到主題。為什麼在 dotnet core 一系都有此要求?而且還不是對所有 Win7 系統都有此要求,這是為什麼?回答這兩個問題,可以從 dotnet core 的 dotnet host core run 開始聊起

在 Windows 下,咱輕按兩下運作的 dotnet core 的可執行 exe 檔案,其實是一個 AppHost 檔案。咱編寫的 Main 函數,在非單檔案模式下,是放在同名的 dll 裡面。詳細關于 AppHost 請參閱 dotnet core 應用是如何跑起來的 通過AppHost了解運作過程

在 dotnet host core run 裡,對應代碼是

src\coreclr\hosts\corerun\corerun.hpp

檔案,在這裡需要拉起

hostpolicy.dll

元件。加載此元件的代碼如下,不過代碼内容不重要哈

inline bool try_load_hostpolicy(pal::string_t mock_hostpolicy_value)
    {
        const char_t* hostpolicyName = W("hostpolicy.dll");
        pal::mod_t hMod = (pal::mod_t)::GetModuleHandleW(hostpolicyName);
        if (hMod != nullptr)
            return true;

        // Check if a hostpolicy exists and if it does, load it.
        if (pal::does_file_exist(mock_hostpolicy_value))
            hMod = (pal::mod_t)::LoadLibraryExW(mock_hostpolicy_value.c_str(), nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);

        if (hMod == nullptr)
            pal::fprintf(stderr, W("Failed to load mock hostpolicy at path '%s'. Error: 0x%08x\n"), mock_hostpolicy_value.c_str(), ::GetLastError());

        return hMod != nullptr;
    }
           

以上最關鍵的隻有這一行代碼

LoadLibraryExW(mock_hostpolicy_value.c_str(), nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)

以上代碼通過 LoadLibraryExW 函數進行加載庫。調用時傳入了

LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR

參數。根據官方文檔的描述,調用此函數,如果加入了

LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR

參數,将要求 KB2533623 更新檔

If this value is used, the directory that contains the DLL is temporarily added to the beginning of the list of directories that are searched for the DLL's dependencies. Directories in the standard search path are not searched.

The lpFileName parameter must specify a fully qualified path. This value cannot be combined with LOAD_WITH_ALTERED_SEARCH_PATH.

For example, if Lib2.dll is a dependency of C:\Dir1\Lib1.dll, loading Lib1.dll with this value causes the system to search for Lib2.dll only in C:\Dir1. To search for Lib2.dll in C:\Dir1 and all of the directories in the DLL search path, combine this value with LOAD_LIBRARY_DEFAULT_DIRS.

Windows 7, Windows Server 2008 R2, Windows Vista and Windows Server 2008: This value requires KB2533623 to be installed.

Windows Server 2003 and Windows XP: This value is not supported

除以上邏輯之外,在 dotnet 倉庫裡還可以找到其他的各個部分的 LoadLibraryExW 函數上,也都帶上了

LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR

參數,清單如下:

  • src\coreclr\dlls\dbgshim\dbgshim.cpp

    : CreateDebuggingInterfaceFromVersion2 函數
  • src\coreclr\hosts\corerun\corerun.hpp

    : try_load_hostpolicy 函數
  • src\coreclr\hosts\coreshim\CoreShim.cpp

    : TryLoadHostPolicy 函數
  • src\coreclr\vm\nativelibrary.cpp

    : DetermineLibNameVariations 函數
  • src\native\corehost\hostmisc\pal.windows.cpp

    : load_library 函數

看起來檔案不多,就看哪個夥伴想不開就去改改挖坑吧

此 LoadLibraryExW 函數是放在 Kernel32.dll 的,相同的需求,在 dotnet core 裡也間接依賴于 AddDllDirectory 和 SetDefaultDllDirectories 和 RemoveDllDirectory 等方法。如官方文檔描述,剛好這些方法也都相同依賴 KB2533623 更新檔

Windows 7, Windows Server 2008 R2, Windows Vista and Windows Server 2008: To use this function in an application, call GetProcAddress to retrieve the function's address from Kernel32.dll. KB2533623 must be installed on the target platform.

通過如上描述,可以了解到,在 dotnet core 需要更新檔的原因是調用了 Kernel32.dll 的新(大約10年前加的)函數,對于一些 Win7 舊裝置上,沒有更新 Kernel32.dll 加上函數

是以,判斷 dotnet core 所依賴環境可以有兩個方法,第一個方法是判斷 KB2533623 更新檔是否有安裝,另一個方法是判斷 Kernel32.dll 裡是否存在 AddDllDirectory 函數。第一個方法判斷是不靠譜的,因為不一定需要 KB2533623 更新檔,如上文描述,換成其他幾個更新檔也可以。判斷更新檔安裝可以使用下面指令行代碼,更多請參閱 dotnet 通過 WMI 擷取系統更新檔 和 PowerShell 通過 WMI 擷取更新檔

BAT:

wmic qfe GET hotfixid | findstr /C:"KB2533623"
           

另外,判斷是否存在 KB2533623 更新檔,不存在則安裝的 bat 腳本代碼如下

echo off
cd /d %~dp0
wmic qfe GET hotfixid | findstr /C:"KB2533623"
if %errorlevel% equ 0 (Echo Patch KB2533623 installed) else (
wusa Windows6.1-KB2533623-x64.msu /quiet /norestart
wusa Windows6.1-KB2533623-x86.msu /quiet /norestart
exit /b 1
)
exit /b 0
           

第二個方法我比較推薦,判斷代碼如下:

using PInvoke;
using System;

namespace AddDllDirectoryDetectCs
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var hModule = Kernel32.LoadLibrary("Kernel32.dll"))
            {
                if (!hModule.IsInvalid)
                {
                    IntPtr hFarProc = Kernel32.GetProcAddress(hModule, "AddDllDirectory");
                    if (hFarProc != IntPtr.Zero)
                    {
                        Console.WriteLine("Either running on Win8+, or KB2533623 is installed");
                    }
                    else
                    {
                        Console.Write("Likely running on Win7 or older OS, and KB2533623 is not installed");
                    }
                }
            }

            // 以下是判斷 Universal C Runtime 的邏輯,可以忽略
            using (var hModule = Kernel32.LoadLibraryEx("UCRTBASE.dll", IntPtr.Zero, Kernel32.LoadLibraryExFlags.LOAD_LIBRARY_SEARCH_SYSTEM32))
            {
                if (!hModule.IsInvalid)
                {
                    Console.WriteLine("UCRT is available - Either running on Win10+ or KB2999226 is installed");
                }
                else
                {
                    Console.WriteLine("UCRT is not available - Likely running on OS older than Win10 and KB2999226 is not installed");
                }
            }
        }
    }
}
           

以上代碼由 WPF 架構官方開發 Vatsan Madhavan 大佬提供,請看 vatsan-madhavan/checknetcoreprereqs: Helper utility to check .NET Core Prerequisites on Windows systems

探索 dotnet core 為何在 Windows7 系統需要更新檔的原因

收到 Vatsan Madhavan 大佬贊

參考

LoadLibraryExW function (libloaderapi.h) - Win32 apps Microsoft Docs

LoadLibraryExA function (libloaderapi.h) - Win32 apps Microsoft Docs

LoadLibraryW function (libloaderapi.h) - Win32 apps Microsoft Docs

windows - SetDllDirectory does not cascade, so dependency DLLs cannot be loaded - Stack Overflow

SetDllDirectoryA function (winbase.h) - Win32 apps Microsoft Docs

AddDllDirectory function (libloaderapi.h) - Win32 apps Microsoft Docs

Windows installer should warn about missing required updated · Issue #2452 · dotnet/runtime

RemoveDllDirectory function (libloaderapi.h) - Win32 apps

Unable to load DLL 'wpfgfx_cor3.dll' or one of its dependencies · Issue #2009 · dotnet/wpf

SqlClient: Unable to load DLL 'sni.dll' · Issue #16905 · dotnet/runtime

Failed to bind to coreclr

dotnet run failure on Windows 7 and Windows 2008 R2 · Issue #5590 · dotnet/sdk

API Set Usage Question · Issue #5075 · dotnet/runtime

Cannot load CoreCLR.dll · Issue #5076 · dotnet/runtime

vatsan-madhavan/checknetcoreprereqs: Helper utility to check .NET Core Prerequisites on Windows systems

使用 C# 判斷指定的 Windows 更新是否已安裝-碼農很忙

Windows 7 安裝 msu 系統更新時的常見報錯及解決辦法-碼農很忙

Windows 7 安裝 .NET 5 / .NET Core 3.1 環境的方法和依賴檔案-碼農很忙

Windows 7 SP 1 部署 .NET 6 Desktop Runtime 桌面運作時會遇到的問題-碼農很忙

部落格園部落格隻做備份,部落格釋出就不再更新,如果想看最新部落格,請到 https://blog.lindexi.com/

探索 dotnet core 為何在 Windows7 系統需要更新檔的原因

本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協定進行許可。歡迎轉載、使用、重新釋出,但務必保留文章署名[林德熙](http://blog.csdn.net/lindexi_gd)(包含連結:http://blog.csdn.net/lindexi_gd ),不得用于商業目的,基于本文修改後的作品務必以相同的許可釋出。如有任何疑問,請與我[聯系](mailto:[email protected])。