Search code examples
c++windowsaudiowindows-10directsound

DirectSound crashes due to a read access violation when calling IDirectSoundBuffer8::Play (inside LFQueuePut, a dsound.dll internal function)


I am working on a game, and am sporadically observing crashes inside of dsound.dll.

This happens right after creating a IDirectSoundBuffer8 audio buffer, when calling the Play method on it. At this point I haven't written to the buffer or done anything to the sound system besides creating the audio device, setting the cooperative level, creating the audio buffer and starting to play.

All values returned by DirectSound indicate success, and even when I explicitely query the buffer for things like DSERR_BUFFERLOST, no sign of error is indicated.

This is the callstack:

Callstack


Solution

  • After hunting this bug down by process of elimination, it seems to be a Windows/Driver issue. In short: DirectSound may crash your application if you reserve 10 TiB of address space. I'm using this address space for a debug allocator that helps hunting down use-after frees, and for some reason DirectSound seems to interact with this allocation.

    I was able to produce the following repro, that crashes about every other execution (although I have also seen crash rates as low as 5% in my application). I was able to reproduced this on 2 PCs, and one other person who tried it was also able to reproduce. You have to execute this code in a debugger though, because otherwise you won't be able to distinguish a crash due to access violation from the execution finishing normally:

    #include "dsound.h"
    #include "assert.h"
    
    //link against user32.lib, dsound.lib and dxguid.lib
    
    LRESULT CALLBACK WindowProc(HWND handle, UINT message, WPARAM wParam, LPARAM lParam){
        return DefWindowProcW(handle, message, wParam, lParam);
    }
    
    void main(){
        VirtualAlloc(nullptr, 10llu * 1024 * 1024 * 1024 * 1024, MEM_RESERVE, PAGE_NOACCESS); //if you comment this out, it won't crash
    
        auto application_instance = GetModuleHandle(NULL);
    
        WNDCLASSEXW window_class;
        ZeroMemory(&window_class, sizeof(window_class));
        window_class.cbSize = sizeof(window_class);
        window_class.style = CS_HREDRAW|CS_VREDRAW;
        window_class.lpfnWndProc = WindowProc;
        window_class.hInstance = application_instance;
        window_class.lpszClassName = L"TEST_WINDOW_CLASS";
    
        if(RegisterClassExW(&window_class) == 0)
            assert(false);
    
        auto window_handle = CreateWindowExW(0, window_class.lpszClassName, L"smashing", WS_OVERLAPPEDWINDOW|WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 100, 100, 0, 0, application_instance, 0);
    
        IDirectSound8* active_device = nullptr;
        IDirectSoundBuffer8* active_buffer = nullptr;
    
        if(DirectSoundCreate8(NULL, &active_device, NULL) != DS_OK)
            assert(false);
        if(active_device->SetCooperativeLevel(window_handle, DSSCL_PRIORITY) != DS_OK)
            assert(false);
    
        WAVEFORMATEX format_description;
        format_description.wFormatTag = WAVE_FORMAT_PCM;
        format_description.nChannels = 2;
        format_description.nSamplesPerSec = 44100;
        format_description.nAvgBytesPerSec = 44100 * 4;
        format_description.nBlockAlign = 4;
        format_description.wBitsPerSample = 16;
        format_description.cbSize = 0;
    
        DSBUFFERDESC buffer_description;
        buffer_description.dwSize = sizeof(buffer_description);
        buffer_description.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
        buffer_description.dwBufferBytes = 44100 * 4;
        buffer_description.dwReserved = 0;
        buffer_description.lpwfxFormat = &format_description;
        buffer_description.guid3DAlgorithm = DS3DALG_DEFAULT;
    
        LPDIRECTSOUNDBUFFER buffer_before_cast = nullptr;
    
        if(active_device->CreateSoundBuffer(&buffer_description, &buffer_before_cast, NULL) != DS_OK)
            assert(false);
        if(buffer_before_cast->QueryInterface(IID_IDirectSoundBuffer8, (void **)&active_buffer) != S_OK)
            assert(false);
        if(active_buffer->Play(0, 0, DSBPLAY_LOOPING) != DS_OK) //this is the place where it crashes more often than not
            assert(false);
    }
    

    I have not found a solution to this issue, and since there is no real way to report such a bug to microsoft with any hopes of it actually getting fixed, I decided to put this on Stackoverflow so that other people hopefully don't waste as much time on it as I have. I will probably try using WASAPI instead next.