Warum würde waveOutWrite () eine Ausnahme im Debug-Heap verursachen?

Bei der Recherche zu diesem Thema habe ich das folgende Szenario online mehrfach erwähnt, immer als unbeantwortete Fragen in Programmierforen. Ich hoffe, dass die Veröffentlichung hier zumindest dazu dient, meine Ergebnisse zu dokumentieren.

Erstens das Symptom: Während ich ziemlich normalen Code mit waveOutWrite () zur Ausgabe von PCM-Audio ausführe, erhalte ich manchmal Folgendes, wenn ich unter dem Debugger arbeite:

 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    

Während der offensichtliche Verdacht eine Haufenverfälschung irgendwo anders im Code sein würde, fand ich heraus, dass das nicht der Fall ist. Darüber hinaus konnte ich dieses Problem mithilfe des folgenden Codes reproduzieren (dies ist Teil einer dialogbasierten MFC-Anwendung :)

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;
 }

Bevor sich jemand dazu äußert, ja - der Beispielcode gibt nicht initialisierten Speicher wieder. Versuchen Sie dies nicht, wenn Ihre Lautsprecher ganz aufgedreht sind.

Beim Debuggen wurden die folgenden Informationen angezeigt: waveOutPrepareHeader () füllt header.reserved mit einem Zeiger auf eine Struktur, die als erste zwei Elemente mindestens zwei Zeiger enthält. Der erste Zeiger wird auf NULL gesetzt. Nach dem Aufruf von waveOutWrite () wird dieser Zeiger auf einen Zeiger gesetzt, der auf dem globalen Heap zugeordnet ist. In Pseudocode würde das ungefähr so ​​aussehen:

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 */
}

Normalerweise wird der Header von waveCompleteHeader (), einer internen Funktion von wdmaud.dll, an die Anwendung zurückgegeben. waveCompleteHeader () versucht, die Zuordnung des von waveOutWrite () zugewiesenen Zeigers aufzuheben, indem GlobalHandle () / GlobalUnlock () und friends aufgerufen werden. Manchmal Bomben von GlobalHandle (), wie oben gezeigt.

Der Grund, warum GlobalHandle () -Bomben nicht auf eine Heap-Beschädigung zurückzuführen sind, wie ich zunächst vermutet habe, liegt darin, dass waveOutWrite () zurückgegeben wurde, ohne den ersten Zeiger in der internen Struktur auf einen gültigen Zeiger zu setzen. Ich vermute, dass dadurch der Speicher freigegeben wird, auf den dieser Zeiger zeigt, bevor er zurückkehrt, aber ich habe ihn noch nicht zerlegt.

Dies scheint nur zu passieren, wenn das Wave-Wiedergabesystem nur noch wenige Puffer enthält. Aus diesem Grund verwende ich einen einzelnen Header, um dies wiederzugeben.

Zum jetzigen Zeitpunkt habe ich ein gutes Argument dafür, dass dies ein Fehler in meiner Anwendung ist - schließlich läuft meine Anwendung nicht einmal. Hat das schon mal jemand gesehen?

Ich sehe dies unter Windows XP SP2. Die Audiokarte stammt von SigmaTel und die Treiberversion ist 5.10.0.4995.

Anmerkungen:

Um Verwirrung in der Zukunft zu vermeiden, möchte ich darauf hinweisen, dass die Antwort, dass das Problem in der Verwendung von malloc () / free () zur Verwaltung der gespielten Puffer liegt, einfach falsch ist. Sie werden feststellen, dass ich den obigen Code geändert habe, um den Vorschlag widerzuspiegeln und zu verhindern, dass mehr Personen den gleichen Fehler machen - es macht keinen Unterschied. Der Puffer, der von waveCompleteHeader () freigegeben wird, enthält nicht die PCM-Daten. Die Verantwortung für die Freigabe des PCM-Puffers liegt bei der Anwendung, und es ist nicht erforderlich, dass er auf eine bestimmte Weise zugewiesen wird.

Außerdem stelle ich sicher, dass keiner der von mir verwendeten waveOut-API-Aufrufe fehlschlägt.

Ich gehe momentan davon aus, dass dies entweder ein Fehler in Windows oder im Audiotreiber ist. Abweichende Meinungen sind immer willkommen.

Antworten auf die Frage(9)

Ihre Antwort auf die Frage