dotnet X11 棧空間被回收導致呼叫 XPutShmImage 閃退

lindexi發表於2024-08-23

本文記錄在使用 X11 過程中的問題,由於不正確使用導致棧空間被回收,從而在呼叫 XPutShmImage 時讓應用閃退,此問題本質上講只和 X11 的設計有一分錢關係,更多的問題在於我的寫法上

上一篇部落格 裡,介紹了使用 MIT-SHM 共享記憶體推送圖片,詳細請看:dotnet X11 簡單使用 MIT-SHM 共享記憶體推送圖片

在上一篇部落格裡面是在頂層函式里面完成所有邏輯的,當我準備作為產品級釋出時,我最佳化了一些程式碼,接著我執行程式就收到了以下錯誤資訊

X Error of failed request:  BadShmSeg (invalid shared segment parameter)
  Major opcode of failed request:  130 (MIT-SHM)
  Minor opcode of failed request:  3 (X_ShmPutImage)
  Segment id in failed request:  0x0
  Serial number of failed request:  41
  Current serial number in output stream:  42

或者遇到了以下經典錯誤

Segmentation fault (core dumped)

或如下錯誤

The RX block to map as RW was not found

或如下錯誤

The RW block to unmap was not found

或如下錯誤

corrupted double-linked list

以上這些錯誤都沒有很明確的錯誤點,但綜合以上這些錯誤,如 上述錯誤的 Segment id in failed request: 0x0 就能說明問題,即可能在 XPutShmImage 中遇到類似野指標或指標被覆蓋等問題

特別感謝 lsj 幫忙閱讀和除錯 XLib 和 XServer 的程式碼,幫我找到了是 XImage 裡面記錄的不公開的 obdata 欄位指向了 0x0 的地址或其他錯誤的地址的問題

為了詳細說明此問題的原因,我將貼出我的部分實現程式碼,本文的全部程式碼可以在本文末尾找到程式碼的下載方法

核心的錯誤實現程式碼如下

    private static unsafe XShmInfo CreateXShmInfo(IntPtr display, nint visual, int width, int height, int mapLength)
    {
        ... // 忽略其他程式碼

        const int ZPixmap = 2;
        var xShmSegmentInfo = new XShmSegmentInfo();
        var shmImage = (XImage*)XShmCreateImage(display, visual, 32, ZPixmap, IntPtr.Zero, &xShmSegmentInfo,
            (uint)width, (uint)height);

        Console.WriteLine(
            $"XShmCreateImage = {(IntPtr)shmImage:X} xShmSegmentInfo={xShmSegmentInfo} PXShmCreateImage={new IntPtr(&xShmSegmentInfo):X}");

        var shmgetResult = shmget(IPC_PRIVATE, mapLength, IPC_CREAT | 0777);
        Console.WriteLine($"shmgetResult={shmgetResult:X}");

        xShmSegmentInfo.shmid = shmgetResult;

        var shmaddr = shmat(shmgetResult, IntPtr.Zero, 0);
        Console.WriteLine($"shmaddr={shmaddr:X}");

        xShmSegmentInfo.shmaddr = (char*)shmaddr.ToPointer();
        shmImage->data = shmaddr;

        XShmAttach(display, &xShmSegmentInfo);
        XFlush(display);

        return new XShmInfo(shmImage, shmaddr)
        {
        };
    }

以上程式碼的 XShmInfo 的定義如下

unsafe class XShmInfo
{
    public XShmInfo(XImage* shmImage, IntPtr shmAddr)
    {
        ShmImage = shmImage;
        ShmAddr = shmAddr;
    }

    public XImage* ShmImage { get; }

    public IntPtr ShmAddr { get; }
}

以上的 CreateXShmInfo 存在什麼問題?核心問題在以下這句話

        var xShmSegmentInfo = new XShmSegmentInfo();
        var shmImage = (XImage*)XShmCreateImage(display, visual, 32, ZPixmap, IntPtr.Zero, &xShmSegmentInfo,
            (uint)width, (uint)height);

這裡的 XShmSegmentInfo 是一個結構體,結構體在此方法裡的區域性變數的 new 將會分配在棧上。然而棧上的記憶體將會在方法結束,將會彈棧,從而導致 XShmSegmentInfo 的記憶體地址被覆蓋。呼叫 XShmCreateImage 時候,將 xShmSegmentInfo 區域性變數的地址作為引數。這裡也不能吐槽說 X11 的設計問題,只能說是咱的使用方法不正確。傳入 XShmSegmentInfo 的引數,會將 XShmSegmentInfo 的地址存放到 XImage 裡面記錄的 obdata 欄位。隨著方法執行結束進行彈棧,將讓 XImage 裡面記錄的 obdata 欄位指向錯誤的地址,原本正確的地址空間已經被彈棧抹除,再也沒有哪個地址是正確的地址的了。於是錯誤的 obdata 地址空間導致後續的 XShmPutImage 方法無法正確的使用共享記憶體,如剛好 obdata 指向的地址空間,即原 XShmSegmentInfo 的棧地址空間剛好被寫入 0 的值,那將會遇到 Segment id in failed request: 0x0 錯誤。如果是一個其他的值,則可能導致指標指向飛出去,出現 Segmentation fault (core dumped) 段錯誤,出現 The RW block to unmap was not found 錯誤等

為了更加和大家描述這個問題,我重新根據我產品化的程式碼構建了一個 Demo 專案,本專案的所有程式碼可以在本文末尾找到下載方法

以下程式碼是對 上一篇部落格 裡的程式碼提供的封裝

先定義一個 record 記錄必要的渲染資訊

public record RenderInfo
(
    IntPtr Display,
    IntPtr Visual,
    int Width,
    int Height,
    int DataByteLength,
    IntPtr Handle,
    IntPtr GC
);

再簡單建立一個 X11 視窗,程式碼如下

    XInitThreads();

    var display = XOpenDisplay(IntPtr.Zero);
    var screen = XDefaultScreen(display);
    var rootWindow = XDefaultRootWindow(display);

    XMatchVisualInfo(display, screen, 32, 4, out var info);
    var visual = info.visual;

    var xDisplayWidth = XDisplayWidth(display, screen);
    var xDisplayHeight = XDisplayHeight(display, screen);

    var width = xDisplayWidth;
    var height = xDisplayHeight;

    var valueMask =
            //SetWindowValuemask.BackPixmap
            0
            | SetWindowValuemask.BackPixel
            | SetWindowValuemask.BorderPixel
            | SetWindowValuemask.BitGravity
            | SetWindowValuemask.WinGravity
            | SetWindowValuemask.BackingStore
            | SetWindowValuemask.ColorMap
        //| SetWindowValuemask.OverrideRedirect
        ;
    var xSetWindowAttributes = new XSetWindowAttributes
    {
        backing_store = 1,
        bit_gravity = Gravity.NorthWestGravity,
        win_gravity = Gravity.NorthWestGravity,
        //override_redirect = true, // 設定視窗的override_redirect屬性為True,以避免視窗管理器的干預
        colormap = XCreateColormap(display, rootWindow, visual, 0),
        border_pixel = 0,
        background_pixel = 0,
    };

    var handle = XCreateWindow(display, rootWindow, 0, 0, width, height, 5,
        32,
        (int)CreateWindowArgs.InputOutput,
        visual,
        (nuint)valueMask, ref xSetWindowAttributes);

    XEventMask ignoredMask = XEventMask.SubstructureRedirectMask | XEventMask.ResizeRedirectMask |
                             XEventMask.PointerMotionHintMask;
    var mask = new IntPtr(0xffffff ^ (int)ignoredMask);
    XSelectInput(display, handle, mask);

    XMapWindow(display, handle);
    XFlush(display);

    var gc = XCreateGC(display, handle, 0, 0);

    XFlush(display);

根據以上引數建立 RenderInfo 物件,程式碼如下

    // 一個畫素佔用4個位元組,於是總共的位元組數就是 width * 4 * height 的長度
    var mapLength = width * 4 * height;

    var renderInfo = new RenderInfo(display, visual, width, height, mapLength, handle, gc);

封裝 SHM 相關程式碼到 XShmProvider 型別,建構函式程式碼如下

class XShmProvider
{
    public XShmProvider(RenderInfo renderInfo)
    {
        _renderInfo = renderInfo;
        XShmInfo = Init();
    }

    public XShmInfo XShmInfo { get; }
    private readonly RenderInfo _renderInfo;

    ... // 忽略其他程式碼
}

以上程式碼的 Init 是執行初始化程式碼,返回 XShmInfo 型別物件。在 Init 方法裡面,為了更好復現問題,使用 stackalloc 抬高棧的空間。準確來說這裡應該說降低棧地址空間,這是因為棧地址是向下走的,向低地址方向走的。但大概就是這個意思,大家瞭解就好。為什麼這裡想要抬高棧的空間?原因是為了讓 XShmSegmentInfo 的記憶體地址不被後續壓入方法棧的資料覆蓋,而是能夠被明確的覆蓋,這樣才能比較好復現 Segment id in failed request: 0x0 的情況,防止恰好讀取到一個還能用但是不正確但不爆炸的地址空間,讓介面沒有反應但沒有報錯

    private XShmInfo Init()
    {
        // 嘗試抬高棧的空間
        // 用於讓 XShmSegmentInfo 的記憶體地址不被後續壓入方法棧的資料覆蓋
        Span<byte> span = stackalloc byte[1024];
        Random.Shared.NextBytes(span);

        var renderInfo = _renderInfo;
        var result = CreateXShmInfo(renderInfo.Display, renderInfo.Visual, renderInfo.Width, renderInfo.Height,
            renderInfo.DataByteLength);
        return result;
    }

先在 Init 方法使用 stackalloc 抬高地址空間,接著再呼叫 CreateXShmInfo 方法,在 CreateXShmInfo 方法裡面建立的 XShmSegmentInfo 結構體就在棧上距離方法壓棧地址比較遠的地方。如此一來,等方法執行完成之後,彈棧後,再呼叫新的方法,壓棧,此時也難以將壓入方法棧的資料覆蓋到 XShmSegmentInfo 結構體的地址

以下是 CreateXShmInfo 方法的全部程式碼

    private static unsafe XShmInfo CreateXShmInfo(IntPtr display, nint visual, int width, int height, int mapLength)
    {
        var status = XShmQueryExtension(display);
        if (status == 0)
        {
            throw new Exception("XShmQueryExtension failed"); // 實際使用請換成你的業務異常型別
        }

        status = XShmQueryVersion(display, out var major, out var minor, out var pixmaps);
        Console.WriteLine($"XShmQueryVersion: {status} major={major} minor={minor} pixmaps={pixmaps}");
        if (status == 0)
        {
            throw new Exception("XShmQueryVersion failed"); // 實際使用請換成你的業務異常型別
        }

        const int ZPixmap = 2;
        // 核心問題就是 XShmSegmentInfo 是結構體,在這裡將在棧上分配。後續將使用棧空間的地址傳遞給 XShmCreateImage 方法,然而在此方法執行之後,將會彈棧,導致 XShmSegmentInfo 的記憶體地址被覆蓋。從而讓 XImage 裡面記錄的 obdata 欄位指向錯誤的地址,導致後續的 XShmPutImage 方法無法正確的使用共享記憶體,輸出如下錯誤
        // X Error of failed request:  BadShmSeg (invalid shared segment parameter)
        //   Major opcode of failed request:  130 (MIT-SHM)
        //   Minor opcode of failed request:  3 (X_ShmPutImage)
        //   Segment id in failed request:  0x0
        //   Serial number of failed request:  17
        //   Current serial number in output stream:  17
        // 上述錯誤的 `Segment id in failed request:  0x0` 就說明了問題,即 XImage 裡面記錄的 obdata 欄位指向了 0x0 的地址。常見的錯誤就是類似野指標問題或者指標被覆蓋的問題
        // 在本例中,我們將 XShmSegmentInfo 的在棧上分配的記憶體地址給到 XImage 裡面記錄的 obdata 欄位,方法結束之後,棧空間被覆蓋,導致 obdata 欄位指向了錯誤的地址
        // 為什麼剛好是 0x0 的地址呢?其實原因在於後續的 DoDraw 使用 Span<byte> span = stackalloc byte[1024 * 2]; 強行申請更多的棧空間,從而覆蓋到了 XShmSegmentInfo 的記憶體地址。如果非 DoDraw 強行申請且保持預設為 0 的填充,則這裡的錯誤資訊 Segment id in failed request 的值會更加迷惑,甚至指向的是一個隨機的地址導致 Segmentation fault (core dumped) 段錯誤或 The RX block to map as RW was not found 或 The RW block to unmap was not found 或 corrupted double-linked list 等
        var xShmSegmentInfo = new XShmSegmentInfo();
        var shmImage = (XImage*)XShmCreateImage(display, visual, 32, ZPixmap, IntPtr.Zero, &xShmSegmentInfo,
            (uint)width, (uint)height);

        Console.WriteLine(
            $"XShmCreateImage = {(IntPtr)shmImage:X} xShmSegmentInfo={xShmSegmentInfo} PXShmCreateImage={new IntPtr(&xShmSegmentInfo):X}");

        var shmgetResult = shmget(IPC_PRIVATE, mapLength, IPC_CREAT | 0777);
        Console.WriteLine($"shmgetResult={shmgetResult:X}");

        xShmSegmentInfo.shmid = shmgetResult;

        var shmaddr = shmat(shmgetResult, IntPtr.Zero, 0);
        Console.WriteLine($"shmaddr={shmaddr:X}");

        xShmSegmentInfo.shmaddr = (char*)shmaddr.ToPointer();
        shmImage->data = shmaddr;

        XShmAttach(display, &xShmSegmentInfo);
        XFlush(display);

        return new XShmInfo(shmImage, shmaddr)
        {
            DebugIntPtr = new IntPtr(&xShmSegmentInfo)
        };
    }

以上的 XShmInfo 的 DebugIntPtr 屬性是一個為了除錯 xShmSegmentInfo 的地址空間而新增的除錯屬性。在後續可使用此屬性測試獲取到的地址空間的值

繼續在 XShmProvider 定義 DoDraw 方法,此方法為了更好進行測試,將使用 stackalloc 申請更大的棧空間的大小,確保在 CreateXShmInfo 方法裡面建立的 XShmSegmentInfo 結構體的地址空間被覆蓋到,從而能夠復現問題

class XShmProvider
{
    ... // 忽略其他程式碼

    public unsafe void DoDraw()
    {
        // 申請兩倍於壓棧空間的大小,確保測試地址被覆蓋到,從而能夠復現問題
        Span<byte> span = stackalloc byte[1024 * 2];
        for (int i = 0; i < span.Length; i++)
        {
            span[i] = 0x00;
        }

        Console.WriteLine($"當前除錯程式碼的記憶體 {*((long*)XShmInfo.DebugIntPtr):X}");

        var display = _renderInfo.Display;
        var handle = _renderInfo.Handle;
        var gc = _renderInfo.GC;
        var shmImage = XShmInfo.ShmImage;
        var width = _renderInfo.Width;
        var height = _renderInfo.Height;

        XShmPutImage(display, handle, gc, (XImage*)shmImage, 0, 0, 0, 0, (uint)width, (uint)height, true);

        XFlush(display);
    }

預設在 C# dotnet 裡面申請的 stackalloc 空間都會執行一次清零,除非使用 AllocateUnitializedArray 或 SkipLocalsInitiAttribute 的方式,這就意味著使用 for 迴圈手動清零是完全多餘的。只不過我這裡只是簡單的測試程式碼,我甚至將 0x00 換成 0xCC 的值進行覆蓋,從而測試確保 XShmInfo.DebugIntPtr 的地址空間被覆蓋

以上使用 0xCC 是為了致敬 C++ 的 燙 記憶體,請看 VC中出現“燙”和“屯”的原因(棧區的每一個位元組都被0xCC填充了,也就是int 3h的機器碼,動態分配的堆,VC的Debug用0xCD填充堆的空間,就出現了“屯”) - findumars - 部落格園

回到測試程式碼,在曝光事件呼叫 XShmProvider 的 DoDraw 方法,程式碼如下

    var xShmProvider = new XShmProvider(renderInfo);
    while (true)
    {
        var xNextEvent = XNextEvent(display, out var @event);

        if (xNextEvent != 0)
        {
            break;
        }

        if (@event.type == XEventName.Expose)
        {
            stopwatch.Restart();

            xShmProvider.DoDraw();

            stopwatch.Stop();
        }
    }

執行以上程式碼,預期將會輸出以下資訊

X Error of failed request:  BadShmSeg (invalid shared segment parameter)
  Major opcode of failed request:  130 (MIT-SHM)
  Minor opcode of failed request:  3 (X_ShmPutImage)
  Segment id in failed request:  0x0
  Serial number of failed request:  41
  Current serial number in output stream:  42

透過本文的分析可以看到本文所遇到的 BadShmSeg 錯誤和網上提供的許多機器系統環境的錯誤是不相同的,完全屬於自己寫法的問題,傳入了一個棧上地址空間,最後清空棧導致地址空間記錄錯誤資訊

本文的 Program.cs 程式碼如下

// See https://aka.ms/new-console-template for more information

using System.Diagnostics;
using CPF.Linux;
using static CPF.Linux.XLib;
using static CPF.Linux.XShm;
using static CPF.Linux.LibC;

unsafe
{
    XInitThreads();

    var display = XOpenDisplay(IntPtr.Zero);
    var screen = XDefaultScreen(display);
    var rootWindow = XDefaultRootWindow(display);

    XMatchVisualInfo(display, screen, 32, 4, out var info);
    var visual = info.visual;

    var xDisplayWidth = XDisplayWidth(display, screen);
    var xDisplayHeight = XDisplayHeight(display, screen);

    var width = xDisplayWidth;
    var height = xDisplayHeight;

    var valueMask =
            //SetWindowValuemask.BackPixmap
            0
            | SetWindowValuemask.BackPixel
            | SetWindowValuemask.BorderPixel
            | SetWindowValuemask.BitGravity
            | SetWindowValuemask.WinGravity
            | SetWindowValuemask.BackingStore
            | SetWindowValuemask.ColorMap
        //| SetWindowValuemask.OverrideRedirect
        ;
    var xSetWindowAttributes = new XSetWindowAttributes
    {
        backing_store = 1,
        bit_gravity = Gravity.NorthWestGravity,
        win_gravity = Gravity.NorthWestGravity,
        //override_redirect = true, // 設定視窗的override_redirect屬性為True,以避免視窗管理器的干預
        colormap = XCreateColormap(display, rootWindow, visual, 0),
        border_pixel = 0,
        background_pixel = 0,
    };

    var handle = XCreateWindow(display, rootWindow, 0, 0, width, height, 5,
        32,
        (int)CreateWindowArgs.InputOutput,
        visual,
        (nuint)valueMask, ref xSetWindowAttributes);

    XEventMask ignoredMask = XEventMask.SubstructureRedirectMask | XEventMask.ResizeRedirectMask |
                             XEventMask.PointerMotionHintMask;
    var mask = new IntPtr(0xffffff ^ (int)ignoredMask);
    XSelectInput(display, handle, mask);

    XMapWindow(display, handle);
    XFlush(display);

    var gc = XCreateGC(display, handle, 0, 0);

    XFlush(display);

    Task.Run(() =>
    {
        var newDisplay = XOpenDisplay(IntPtr.Zero);

        while (true)
        {
            Console.ReadLine();

            var xEvent = new XEvent
            {
                ExposeEvent =
                {
                    type = XEventName.Expose,
                    send_event = true,
                    window = handle,
                    count = 1,
                    display = newDisplay,
                    x = 0,
                    y = 0,
                    width = width,
                    height = height,
                }
            };
            // [Xlib Programming Manual: Expose Events](https://tronche.com/gui/x/xlib/events/exposure/expose.html )
            XLib.XSendEvent(newDisplay, handle, propagate: false,
                new IntPtr((int)(EventMask.ExposureMask)),
                ref xEvent);

            XFlush(newDisplay);
        }

        XCloseDisplay(newDisplay);
    });

    var stopwatch = new Stopwatch();

    // 一個畫素佔用4個位元組,於是總共的位元組數就是 width * 4 * height 的長度
    var mapLength = width * 4 * height;
    //Console.WriteLine($"Length = {mapLength}");

    var renderInfo = new RenderInfo(display, visual, width, height, mapLength, handle, gc);
    var xShmProvider = new XShmProvider(renderInfo);
    while (true)
    {
        var xNextEvent = XNextEvent(display, out var @event);

        if (xNextEvent != 0)
        {
            break;
        }

        if (@event.type == XEventName.Expose)
        {
            stopwatch.Restart();

            xShmProvider.DoDraw();

            stopwatch.Stop();
        }
        else if ((int)@event.type == 65 /*XShmCompletionEvent*/)
        {
        }
    }
}

Console.WriteLine("Hello, World!");

public record RenderInfo
(
    IntPtr Display,
    IntPtr Visual,
    int Width,
    int Height,
    int DataByteLength,
    IntPtr Handle,
    IntPtr GC
);

class XShmProvider
{
    public XShmProvider(RenderInfo renderInfo)
    {
        _renderInfo = renderInfo;
        XShmInfo = Init();
    }

    public XShmInfo XShmInfo { get; }
    private readonly RenderInfo _renderInfo;

    private XShmInfo Init()
    {
        // 嘗試抬高棧的空間
        // 用於讓 XShmSegmentInfo 的記憶體地址不被後續壓入方法棧的資料覆蓋
        Span<byte> span = stackalloc byte[1024];
        Random.Shared.NextBytes(span);

        var renderInfo = _renderInfo;
        var result = CreateXShmInfo(renderInfo.Display, renderInfo.Visual, renderInfo.Width, renderInfo.Height,
            renderInfo.DataByteLength);
        return result;
    }

    private static unsafe XShmInfo CreateXShmInfo(IntPtr display, nint visual, int width, int height, int mapLength)
    {
        var status = XShmQueryExtension(display);
        if (status == 0)
        {
            throw new Exception("XShmQueryExtension failed"); // 實際使用請換成你的業務異常型別
        }

        status = XShmQueryVersion(display, out var major, out var minor, out var pixmaps);
        Console.WriteLine($"XShmQueryVersion: {status} major={major} minor={minor} pixmaps={pixmaps}");
        if (status == 0)
        {
            throw new Exception("XShmQueryVersion failed"); // 實際使用請換成你的業務異常型別
        }

        const int ZPixmap = 2;
        // 核心問題就是 XShmSegmentInfo 是結構體,在這裡將在棧上分配。後續將使用棧空間的地址傳遞給 XShmCreateImage 方法,然而在此方法執行之後,將會彈棧,導致 XShmSegmentInfo 的記憶體地址被覆蓋。從而讓 XImage 裡面記錄的 obdata 欄位指向錯誤的地址,導致後續的 XShmPutImage 方法無法正確的使用共享記憶體,輸出如下錯誤
        // X Error of failed request:  BadShmSeg (invalid shared segment parameter)
        //   Major opcode of failed request:  130 (MIT-SHM)
        //   Minor opcode of failed request:  3 (X_ShmPutImage)
        //   Segment id in failed request:  0x0
        //   Serial number of failed request:  17
        //   Current serial number in output stream:  17
        // 上述錯誤的 `Segment id in failed request:  0x0` 就說明了問題,即 XImage 裡面記錄的 obdata 欄位指向了 0x0 的地址。常見的錯誤就是類似野指標問題或者指標被覆蓋的問題
        // 在本例中,我們將 XShmSegmentInfo 的在棧上分配的記憶體地址給到 XImage 裡面記錄的 obdata 欄位,方法結束之後,棧空間被覆蓋,導致 obdata 欄位指向了錯誤的地址
        // 為什麼剛好是 0x0 的地址呢?其實原因在於後續的 DoDraw 使用 Span<byte> span = stackalloc byte[1024 * 2]; 強行申請更多的棧空間,從而覆蓋到了 XShmSegmentInfo 的記憶體地址。如果非 DoDraw 強行申請且保持預設為 0 的填充,則這裡的錯誤資訊 Segment id in failed request 的值會更加迷惑,甚至指向的是一個隨機的地址導致 Segmentation fault (core dumped) 段錯誤或 The RX block to map as RW was not found 或 The RW block to unmap was not found 或 corrupted double-linked list 等
        var xShmSegmentInfo = new XShmSegmentInfo();
        var shmImage = (XImage*)XShmCreateImage(display, visual, 32, ZPixmap, IntPtr.Zero, &xShmSegmentInfo,
            (uint)width, (uint)height);

        Console.WriteLine(
            $"XShmCreateImage = {(IntPtr)shmImage:X} xShmSegmentInfo={xShmSegmentInfo} PXShmCreateImage={new IntPtr(&xShmSegmentInfo):X}");

        var shmgetResult = shmget(IPC_PRIVATE, mapLength, IPC_CREAT | 0777);
        Console.WriteLine($"shmgetResult={shmgetResult:X}");

        xShmSegmentInfo.shmid = shmgetResult;

        var shmaddr = shmat(shmgetResult, IntPtr.Zero, 0);
        Console.WriteLine($"shmaddr={shmaddr:X}");

        xShmSegmentInfo.shmaddr = (char*)shmaddr.ToPointer();
        shmImage->data = shmaddr;

        XShmAttach(display, &xShmSegmentInfo);
        XFlush(display);

        return new XShmInfo(shmImage, shmaddr)
        {
            DebugIntPtr = new IntPtr(&xShmSegmentInfo)
        };
    }

    public unsafe void DoDraw()
    {
        // 申請兩倍於壓棧空間的大小,確保測試地址被覆蓋到,從而能夠復現問題
        Span<byte> span = stackalloc byte[1024 * 2];
        for (int i = 0; i < span.Length; i++)
        {
            span[i] = 0x00;
        }

        Console.WriteLine($"當前除錯程式碼的記憶體 {*((long*)XShmInfo.DebugIntPtr):X}");

        var display = _renderInfo.Display;
        var handle = _renderInfo.Handle;
        var gc = _renderInfo.GC;
        var shmImage = XShmInfo.ShmImage;
        var width = _renderInfo.Width;
        var height = _renderInfo.Height;

        XShmPutImage(display, handle, gc, (XImage*)shmImage, 0, 0, 0, 0, (uint)width, (uint)height, true);

        XFlush(display);
    }
}

unsafe class XShmInfo
{
    public XShmInfo(XImage* shmImage, IntPtr shmAddr)
    {
        ShmImage = shmImage;
        ShmAddr = shmAddr;
    }

    public XImage* ShmImage { get; }

    public IntPtr ShmAddr { get; }

    public IntPtr DebugIntPtr { set; get; }
}

本文程式碼放在 githubgitee 上,可以使用如下命令列拉取程式碼。我整個程式碼倉庫比較龐大,使用以下命令列可以進行部分拉取,拉取速度比較快

先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 843c229a5b590f2c1149050d0f203f53dd48f6b6

以上使用的是國內的 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源。請在命令列繼續輸入以下程式碼,將 gitee 源換成 github 源進行拉取程式碼。如果依然拉取不到程式碼,可以發郵件向我要程式碼

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 843c229a5b590f2c1149050d0f203f53dd48f6b6

獲取程式碼之後,進入 X11/DemdarhairdaKacaycheecheeleco 資料夾,即可獲取到原始碼

更多技術部落格,請參閱 部落格導航

參考文件:

dotnet X11 簡單使用 MIT-SHM 共享記憶體推送圖片

Jammy CI tests failing with: "X Error of failed request: BadShmSeg (invalid shared segment parameter)" · Issue #18726 · RobotLocomotion/drake

18.04 - How to fix X Error: BadAccess, BadDrawable, BadShmSeg while running graphical application using Docker? - Ask Ubuntu

got 'X Error of failed request: BadShmSeg (invalid shared segment parameter)' · Issue #234 · termux/termux-x11

善用 XShm Extension 加速貼圖

C++ - 面向基於堆疊的緩衝區保護的 Visual C++ 支援 - Microsoft Learn

相關文章