日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[原]调试PInvoke导致的内存破坏

發布時間:2023/12/4 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [原]调试PInvoke导致的内存破坏 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

緣起

最近項目中遇到一個詭異的問題,程序在升級到.net4.6.1后,執行某個功能時會崩潰,提示訪問只讀內存區。大概規律如下:

  • debug版不崩潰,release版穩定崩潰。

  • 只有x64位的程序崩潰,32位及anycpu編譯出來的程序運行不會崩潰。

  • 出問題的代碼范圍很小(按鈕點擊事件代碼不多)。

  • 根據以上信息,各位小伙伴有什么思路嗎?

    排查

    由于release版可以穩定重現,而且范圍不大,故通過二分法(每次注釋掉一半代碼,看看是否崩潰,如果崩潰,接著注釋掉一半代碼,如果不崩潰說明崩潰跟注釋掉的那段代碼有關...)很快定位到了導致問題的代碼。

    最后發現并不是由于升級.net版本導致的,而是程序本身的問題:

    代碼中通過P/Invoke調用了原生 API GlobalMemoryStatus()。在定義MemoryStatus結構體的時候強制按4字節定義了每一個字段。而在x64下MemoryStatus結構體中的成員有些不是4字節大小,而是8字節大小!這樣,傳遞給GlobalMemoryStatus()的MemoryStatus參數(32字節)比GlobalMemoryStatus()預期的(56字節)小,導致GlobalMemoryStatus寫了不該寫的內存!????????????

    重現

    我把有問題的代碼獨立出來了,完整的測試代碼如下(請編譯x64版本):

    using System;
    using System.Runtime.InteropServices;
    namespace ConsoleApplication1
    {
    class Program
    {
    [StructLayout(LayoutKind.Sequential)]
    public struct MemoryStatus
    {
    [MarshalAs(UnmanagedType.U4)]
    public uint dwLength;
    [MarshalAs(UnmanagedType.U4)]
    public uint dwMemoryLoad;
    [MarshalAs(UnmanagedType.U4)]
    public uint dwTotalPhys;
    [MarshalAs(UnmanagedType.U4)]
    public uint dwAvailPhys;
    [MarshalAs(UnmanagedType.U4)]
    public uint dwTotalPageFile;
    [MarshalAs(UnmanagedType.U4)]
    public uint dwAvailPageFile;
    [MarshalAs(UnmanagedType.U4)]
    public uint dwTotalVirtual;
    [MarshalAs(UnmanagedType.U4)]
    public uint dwAvailVirtual;
    }

    [DllImport("kernel32.dll")]
    public static extern void GlobalMemoryStatus(ref MemoryStatus memoryStatus);
    class CMyClass
    {
    public int n1 = 0;
    }
    struct CMyStruct
    {
    public CMyClass data;
    }
    static void Main(string[] args)
    {
    CMyStruct myObj = new CMyStruct(); myObj.data = new CMyClass();
    MemoryStatus memoryStatus = new MemoryStatus();
    // this line will corrupt the stack if we run in x64.
    // because memoryStatus is defined on the stack.
    GlobalMemoryStatus(ref memoryStatus);
    // myObj.data is corrupted
    System.Console.WriteLine("{0}", myObj.data);
    }
    }
    }

    修復

    只需要定義MemoryStatus的時候,注意字段的大小即可。正確的MemoryStatus定義如下:

    public struct MemoryStatus
    {
    [MarshalAs(UnmanagedType.U4)]
    public uint dwLength;
    [MarshalAs(UnmanagedType.U4)]
    public uint dwMemoryLoad;
    // 以下字段 4 bytes on 32-bit Windows, 8 bytes on 64-bit Windows.
    [MarshalAs(UnmanagedType.SysUInt)]
    public IntPtr dwTotalPhys;
    [MarshalAs(UnmanagedType.SysUInt)]
    public IntPtr dwAvailPhys;
    [MarshalAs(UnmanagedType.SysUInt)]
    public IntPtr dwTotalPageFile;
    [MarshalAs(UnmanagedType.SysUInt)]
    public IntPtr dwAvailPageFile;
    [MarshalAs(UnmanagedType.SysUInt)]
    public IntPtr dwTotalVirtual;
    [MarshalAs(UnmanagedType.SysUInt)]
    public IntPtr dwAvailVirtual;
    }

    思考

    • 為什么debug版不崩潰?而release版會崩潰?

      我在測試機器上調查的原因是debug版本運行的時候,關鍵內存恰巧沒被破壞(太“幸運”或者太不幸了),而在release版本中暴露了問題。可能在其它機器上debug版本也會崩潰或者發生其它詭異的問題。

      說明:測試代碼與項目中的實際代碼不一樣,有可能現象不一樣,但問題的本質是一樣的。

    • 為什么運行Any CPU編譯出來的程序不崩潰?

      當Platform target是Any CPU的時候,在工程屬性,Build下的Prefer 32-bit的選項默認是勾選的,編譯的程序會作為 32 位進程運行,所以不會崩潰。如果取消勾選,則編譯出來的程序會作為 64 位應用程序運行,會崩潰。

    build settings


    關于Platform target的作用,具體參考《CLR via C#》,下圖是從《CLR via C#》中文版第 4 版上截取的。


    /platform option 截自《CLR via C#》

    總結

    .net程序中,令人頭疼的內存破壞問題很難出現了,這極大的提高了程序的穩定性。如果出現堆破壞,很有可能跟P/Invoke或者unsafe代碼相關,可以重點排查相關代碼。

    啟用托管調試助手(Managed Debugging Assistants, 下文簡稱MDAs) 有時候會對調試問題有極大的幫助,雖然我這次調試沒有借助MDAs,但我第一個想到的就是MDAs。

    關于MDAs的介紹請參考參考資料第一條

    參考資料

    • Managed Debugging Assistants[1]

    • GlobalMemoryStatus[2]

    • 《CLR via C#》[3]

    References

    [1]? Managed Debugging Assistants:
    https://docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/diagnosing-errors-with-managed-debugging-assistants

    [2]? GlobalMemoryStatus:
    https://docs.microsoft.com/zh-cn/windows/win32/api/winbase/nf-winbase-globalmemorystatus?redirectedfrom=MSDN

    [3] 《CLR via C#》:

    https://book.douban.com/subject/4924165/

    寫留言

    總結

    以上是生活随笔為你收集整理的[原]调试PInvoke导致的内存破坏的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。