Dlaczego waveOutWrite () powodowałby wyjątek w stercie debugowania?

Podczas badania tego problemu znalazłem wiele wzmianek o następującym scenariuszu online, zawsze jako pytania bez odpowiedzi na forach programistycznych. Mam nadzieję, że opublikowanie tego tutaj przynajmniej posłuży do udokumentowania moich ustaleń.

Po pierwsze, symptom: Podczas uruchamiania całkiem standardowego kodu, który używa waveOutWrite () do wysyłania dźwięku PCM, czasami dostaję to podczas pracy pod debuggerem:

 ntdll.dll!_DbgBreakPoint@0()   
 ntdll.dll!_RtlpBreakPointHeap@4()  + 0x28 bytes    
 ntdll.dll!_RtlpValidateHeapEntry@12()  + 0x113 bytes   
 ntdll.dll!_RtlDebugGetUserInfoHeap@20()  + 0x96 bytes  
 ntdll.dll!_RtlGetUserInfoHeap@20()  + 0x32743 bytes    
 kernel32.dll!_GlobalHandle@4()  + 0x3a bytes   
 wdmaud.drv!_waveCompleteHeader@4()  + 0x40 bytes   
 wdmaud.drv!_waveThread@4()  + 0x9c bytes   
 kernel32.dll!_BaseThreadStart@8()  + 0x37 bytes    

Chociaż oczywistym podejrzanym byłoby uszkodzenie stosu w innym miejscu kodu, dowiedziałem się, że tak nie jest. Ponadto udało mi się odtworzyć ten problem za pomocą następującego kodu (jest to część aplikacji MFC opartej na dialogu :)

void CwaveoutDlg::OnBnClickedButton1()
{
    WAVEFORMATEX wfx;
    wfx.nSamplesPerSec = 44100; /* sample rate */
    wfx.wBitsPerSample = 16; /* sample size */
    wfx.nChannels = 2;
    wfx.cbSize = 0; /* size of _extra_ info */
    wfx.wFormatTag = WAVE_FORMAT_PCM;
    wfx.nBlockAlign = (wfx.wBitsPerSample >> 3) * wfx.nChannels;
    wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;

    waveOutOpen(&hWaveOut, 
                WAVE_MAPPER, 
                &wfx,  
                (DWORD_PTR)m_hWnd, 
                0,
                CALLBACK_WINDOW );

    ZeroMemory(&header, sizeof(header));
    header.dwBufferLength = 4608;
    header.lpData = (LPSTR)GlobalLock(GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT, 4608));

    waveOutPrepareHeader(hWaveOut, &header, sizeof(header));
    waveOutWrite(hWaveOut, &header, sizeof(header));
}

afx_msg LRESULT CwaveoutDlg::OnWOMDone(WPARAM wParam, LPARAM lParam)
{
    HWAVEOUT dev = (HWAVEOUT)wParam;
    WAVEHDR *hdr = (WAVEHDR*)lParam;
    waveOutUnprepareHeader(dev, hdr, sizeof(WAVEHDR));
    GlobalFree(GlobalHandle(hdr->lpData));
    ZeroMemory(hdr, sizeof(*hdr));
    hdr->dwBufferLength = 4608;
    hdr->lpData = (LPSTR)GlobalLock(GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT, 4608));
    waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
    waveOutWrite(hWaveOut, hdr, sizeof(WAVEHDR));
    return 0;
 }

Zanim ktokolwiek to komentuje, tak - przykładowy kod odtwarza niezainicjowaną pamięć. Nie próbuj tego z głośnikami obróconymi do góry.

Niektóre debugowanie ujawniło następujące informacje: waveOutPrepareHeader () zapełnia nagłówek.reserved wskaźnikiem do czegoś, co wydaje się być strukturą zawierającą co najmniej dwa wskaźniki jako pierwsze dwa elementy. Pierwszy wskaźnik jest ustawiony na NULL. Po wywołaniu waveOutWrite () wskaźnik ten jest ustawiony na wskaźnik przydzielony na globalnej stercie. W pseudokodzie wyglądałoby to mniej więcej tak:

struct Undocumented { void *p1, *p2; } /* This might have more members */

MMRESULT waveOutPrepareHeader( handle, LPWAVEHDR hdr, ...) {
    hdr->reserved = (Undocumented*)calloc(sizeof(Undocumented));
    /* Do more stuff... */
}

MMRESULT waveOutWrite( handle, LPWAVEHDR hdr, ...) {

    /* The following assignment fails rarely, causing the problem: */
    hdr->reserved->p1 = malloc( /* chunk of private data */ );
    /* Probably more code to initiate playback */
}

Zwykle nagłówek jest zwracany do aplikacji przez waveCompleteHeader (), funkcję wewnętrzną do wdmaud.dll. waveCompleteHeader () próbuje zwolnić wskaźnik przydzielony przez waveOutWrite () wywołując GlobalHandle () / GlobalUnlock () i znajomych. Czasami bomby GlobalHandle (), jak pokazano powyżej.

Powodem, dla którego bomby GlobalHandle () nie są spowodowane uszkodzeniem sterty, jak podejrzewałem na początku - jest to, że waveOutWrite () zwróciło bez ustawienia pierwszego wskaźnika w strukturze wewnętrznej na poprawny wskaźnik. Podejrzewam, że uwalnia pamięć wskazywaną przez ten wskaźnik przed powrotem, ale jeszcze go nie zdemontowałem.

Wydaje się to zdarzyć tylko wtedy, gdy system odtwarzania fal ma mało buforów, dlatego używam jednego nagłówka do odtworzenia tego.

W tym momencie mam całkiem niezły argument przeciwko temu, że jest to błąd w mojej aplikacji - w końcu moja aplikacja nie działa. Czy ktoś to widział wcześniej?

Widzę to na Windows XP SP2. Karta dźwiękowa pochodzi z SigmaTel, a wersja sterownika to 5.10.0.4995.

Uwagi:

Aby zapobiec nieporozumieniom w przyszłości, chciałbym podkreślić, że odpowiedź sugerująca, że ​​problem leży w użyciu malloc () / free () do zarządzania odtwarzanymi buforami, jest po prostu błędna. Zauważysz, że zmieniłem powyższy kod, aby odzwierciedlić sugestię, aby zapobiec popełnieniu tego samego błędu przez większą liczbę osób - nie ma to znaczenia. Bufor zwolniony przez waveCompleteHeader () nie jest tym, który zawiera dane PCM, odpowiedzialność za uwolnienie bufora PCM spoczywa na aplikacji i nie ma wymogu, aby był przydzielany w jakikolwiek konkretny sposób.

Ponadto upewniam się, że żaden z wywołań API waveOut, których używam, nie powiedzie się.

Obecnie zakładam, że jest to błąd w systemie Windows lub w sterowniku audio. Opinie odrębne są zawsze mile widziane.

questionAnswers(9)

yourAnswerToTheQuestion