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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

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

緣起

最近項(xiàng)目中遇到一個(gè)詭異的問題,程序在升級(jí)到.net4.6.1后,執(zhí)行某個(gè)功能時(shí)會(huì)崩潰,提示訪問只讀內(nèi)存區(qū)。大概規(guī)律如下:

  • debug版不崩潰,release版穩(wěn)定崩潰。

  • 只有x64位的程序崩潰,32位及anycpu編譯出來的程序運(yùn)行不會(huì)崩潰。

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

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

    排查

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

    最后發(fā)現(xiàn)并不是由于升級(jí).net版本導(dǎo)致的,而是程序本身的問題:

    代碼中通過P/Invoke調(diào)用了原生 API GlobalMemoryStatus()。在定義MemoryStatus結(jié)構(gòu)體的時(shí)候強(qiáng)制按4字節(jié)定義了每一個(gè)字段。而在x64下MemoryStatus結(jié)構(gòu)體中的成員有些不是4字節(jié)大小,而是8字節(jié)大小!這樣,傳遞給GlobalMemoryStatus()的MemoryStatus參數(shù)(32字節(jié))比GlobalMemoryStatus()預(yù)期的(56字節(jié))小,導(dǎo)致GlobalMemoryStatus寫了不該寫的內(nèi)存!????????????

    重現(xiàn)

    我把有問題的代碼獨(dú)立出來了,完整的測(cè)試代碼如下(請(qǐng)編譯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);
    }
    }
    }

    修復(fù)

    只需要定義MemoryStatus的時(shí)候,注意字段的大小即可。正確的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版會(huì)崩潰?

      我在測(cè)試機(jī)器上調(diào)查的原因是debug版本運(yùn)行的時(shí)候,關(guān)鍵內(nèi)存恰巧沒被破壞(太“幸運(yùn)”或者太不幸了),而在release版本中暴露了問題。可能在其它機(jī)器上debug版本也會(huì)崩潰或者發(fā)生其它詭異的問題。

      說明:測(cè)試代碼與項(xiàng)目中的實(shí)際代碼不一樣,有可能現(xiàn)象不一樣,但問題的本質(zhì)是一樣的。

    • 為什么運(yùn)行Any CPU編譯出來的程序不崩潰?

      當(dāng)Platform target是Any CPU的時(shí)候,在工程屬性,Build下的Prefer 32-bit的選項(xiàng)默認(rèn)是勾選的,編譯的程序會(huì)作為 32 位進(jìn)程運(yùn)行,所以不會(huì)崩潰。如果取消勾選,則編譯出來的程序會(huì)作為 64 位應(yīng)用程序運(yùn)行,會(huì)崩潰。

    build settings


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


    /platform option 截自《CLR via C#》

    總結(jié)

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

    啟用托管調(diào)試助手(Managed Debugging Assistants, 下文簡(jiǎn)稱MDAs) 有時(shí)候會(huì)對(duì)調(diào)試問題有極大的幫助,雖然我這次調(diào)試沒有借助MDAs,但我第一個(gè)想到的就是MDAs。

    關(guān)于MDAs的介紹請(qǐng)參考參考資料第一條

    參考資料

    • 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/

    寫留言

    總結(jié)

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

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。