windows 为什么 waveOutWrite() 会在调试堆中导致异常?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/195696/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why would waveOutWrite() cause an exception in the debug heap?
提问by Ori Pessach
While researching this issue, I found multiple mentions of the following scenario online, invariably as unanswered questions on programming forums. I hope that posting this here will at least serve to document my findings.
在研究这个问题时,我发现在网上多次提到以下场景,总是作为编程论坛上未回答的问题。我希望在这里张贴这个至少可以用来记录我的发现。
First, the symptom: While running pretty standard code that uses waveOutWrite() to output PCM audio, I sometimes get this when running under the debugger:
首先,症状:在运行使用 waveOutWrite() 输出 PCM 音频的非常标准的代码时,我有时在调试器下运行时得到这个:
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
While the obvious suspect would be a heap corruption somewhere else in the code, I found out that that's not the case. Furthermore, I was able to reproduce this problem using the following code (this is part of a dialog based MFC application:)
虽然明显的嫌疑人是代码中其他地方的堆损坏,但我发现情况并非如此。此外,我能够使用以下代码重现此问题(这是基于对话框的 MFC 应用程序的一部分:)
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;
}
Before anyone comments on this, yes - the sample code plays back uninitialized memory. Don't try this with your speakers turned all the way up.
在任何人对此发表评论之前,是的 - 示例代码回放未初始化的内存。不要在扬声器完全打开的情况下尝试此操作。
Some debugging revealed the following information: waveOutPrepareHeader() populates header.reserved with a pointer to what appears to be a structure containing at least two pointers as its first two members. The first pointer is set to NULL. After calling waveOutWrite(), this pointer is set to a pointer allocated on the global heap. In pseudo code, that would look something like this:
一些调试揭示了以下信息: waveOutPrepareHeader() 使用一个指针填充 header.reserved ,该指针指向一个似乎包含至少两个指针作为其前两个成员的结构。第一个指针设置为 NULL。调用waveOutWrite()后,这个指针被设置为一个分配在全局堆上的指针。在伪代码中,它看起来像这样:
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 */
}
Normally, the header is returned to the application by waveCompleteHeader(), a function internal to wdmaud.dll. waveCompleteHeader() tries to deallocate the pointer allocated by waveOutWrite() by calling GlobalHandle()/GlobalUnlock() and friends. Sometimes, GlobalHandle() bombs, as shown above.
通常,标头由 wdmaud.dll 的内部函数 waveCompleteHeader() 返回给应用程序。waveCompleteHeader() 尝试通过调用 GlobalHandle()/GlobalUnlock() 和朋友来释放由 waveOutWrite() 分配的指针。有时, GlobalHandle() 会爆炸,如上所示。
Now, the reason that GlobalHandle() bombs is not due to a heap corruption, as I suspected at first - it's because waveOutWrite() returned without setting the first pointer in the internal structure to a valid pointer. I suspect that it frees the memory pointed to by that pointer before returning, but I haven't disassembled it yet.
现在, GlobalHandle() 炸弹的原因不是堆损坏,正如我最初怀疑的那样 - 这是因为 waveOutWrite() 返回时没有将内部结构中的第一个指针设置为有效指针。我怀疑它在返回之前释放了该指针指向的内存,但我还没有反汇编它。
This only appears to happen when the wave playback system is low on buffers, which is why I'm using a single header to reproduce this.
这似乎只发生在波形播放系统的缓冲区不足时,这就是为什么我使用单个标题来重现这一点。
At this point I have a pretty good case against this being a bug in my application - after all, my application is not even running. Has anyone seen this before?
在这一点上,我有一个很好的理由反对这是我的应用程序中的错误 - 毕竟,我的应用程序甚至没有运行。有没有人见过这个?
I'm seeing this on Windows XP SP2. The audio card is from SigmaTel, and the driver version is 5.10.0.4995.
我在 Windows XP SP2 上看到了这个。声卡来自SigmaTel,驱动版本为5.10.0.4995。
Notes:
笔记:
To prevent confusion in the future, I'd like to point out that the answer suggesting that the problem lies with the use of malloc()/free() to manage the buffers being played is simply wrong. You'll note that I changed the code above to reflect the suggestion, to prevent more people from making the same mistake - it doesn't make a difference. The buffer being freed by waveCompleteHeader() is not the one containing the PCM data, the responsibility to free the PCM buffer lies with the application, and there's no requirement that it be allocated in any specific way.
为了防止将来出现混淆,我想指出问题在于使用 malloc()/free() 来管理正在播放的缓冲区的答案是完全错误的。您会注意到我更改了上面的代码以反映建议,以防止更多人犯同样的错误 - 这没有任何区别。waveCompleteHeader() 释放的缓冲区不是包含 PCM 数据的缓冲区,释放 PCM 缓冲区的责任在于应用程序,并且不要求以任何特定方式分配它。
Also, I make sure that none of the waveOut API calls I use fail.
此外,我确保我使用的所有 waveOut API 调用都不会失败。
I'm currently assuming that this is either a bug in Windows, or in the audio driver. Dissenting opinions are always welcome.
我目前假设这要么是 Windows 中的错误,要么是音频驱动程序中的错误。反对意见总是受欢迎的。
采纳答案by Stefan
You're not alone with this issue: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=100589
您不是一个人遇到这个问题:http: //connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=100589
回答by AShelly
Now, the reason that GlobalHandle() bombs is not due to a heap corruption, as I suspected at first - it's because waveOutWrite() returned without setting the first pointer in the internal structure to a valid pointer. I suspect that it frees the memory pointed to by that pointer before returning, but I haven't disassembled it yet.
现在, GlobalHandle() 炸弹的原因不是堆损坏,正如我最初怀疑的那样 - 这是因为 waveOutWrite() 返回时没有将内部结构中的第一个指针设置为有效指针。我怀疑它在返回之前释放了该指针指向的内存,但我还没有反汇编它。
I can reproduce this with your code on my system. I see something similar to what Johannes reported. After the call to WaveOutWrite, hdr->reserved normally holds a pointer to allocated memory (which appears to contain the wave out device name in unicode, among other things).
我可以在我的系统上用你的代码重现这个。我看到类似于约翰内斯报告的内容。在调用 WaveOutWrite 之后,hdr->reserved 通常保存一个指向已分配内存的指针(其中似乎包含 unicode 中的 wave out 设备名称等)。
But occasionally, after returning from WaveOutWrite(), the byte pointed to by hdr->reserved
is set to 0. This is normally the least significant byte of that pointer. The rest of the bytes in hdr->reserved
are ok, and the block of memory that it normally points to is still allocated and uncorrupted.
但偶尔,从 WaveOutWrite() 返回后,指向的字节hdr->reserved
设置为 0。这通常是该指针的最低有效字节。中的其余字节没问题hdr->reserved
,它通常指向的内存块仍然已分配且未损坏。
It probably is being clobbered by another thread - I can catch the change with a conditional breakpoint immediately after the call to WaveOutWrite(). And the system debug breakpoint is occurring in another thread, not the message handler.
它可能被另一个线程破坏 - 我可以在调用 WaveOutWrite() 后立即使用条件断点捕获更改。并且系统调试断点发生在另一个线程中,而不是消息处理程序中。
However, I can't cause the system debug breakpoint to occur if I use a callback function instead of the windows messsage pump. (fdwOpen = CALLBACK_FUNCTION
in WaveOutOpen() )
When I do it this way, my OnWOMDone handler is called by a different thread - possibly the one that's otherwise responsible for the corruption.
但是,如果我使用回调函数而不是 Windows 消息泵,则无法导致系统调试断点发生。(fdwOpen = CALLBACK_FUNCTION
在 WaveOutOpen() 中)当我这样做时,我的 OnWOMDone 处理程序被一个不同的线程调用 - 可能是另一个负责损坏的线程。
So I think there is a bug, either in windows or the driver, but I think you can work around by handling WOM_DONE with a callback function instead of the windows message pump.
所以我认为 Windows 或驱动程序中存在一个错误,但我认为您可以通过使用回调函数而不是 Windows 消息泵处理 WOM_DONE 来解决。
回答by Stefan
I'm seeing the same problem and have done some analysis myself:
我看到了同样的问题,并自己做了一些分析:
waveOutWrite() allocates (i.e. GlobalAlloc) a pointer to a heap area of 354 bytes and correctly stores it in the data area pointed to by header.reserved.
waveOutWrite() 分配(即GlobalAlloc)一个指向354 字节堆区的指针,并将其正确存储在header.reserved 指向的数据区中。
But when this heap area is to be freed again (in waveCompleteHeader(), according to your analysis; I don't have the symbols for wdmaud.drv myself), the least significant byte of the pointer has been set to zero, thus invalidating the pointer (while the heap is not corrupted yet). In other words, what happens is something like:
但是当这个堆区域再次被释放时(在waveCompleteHeader()中,根据你的分析;我自己没有wdmaud.drv的符号),指针的最低有效字节已被设置为零,从而无效指针(而堆尚未损坏)。换句话说,发生的事情是这样的:
- (BYTE *) (header.reserved) = 0
- (BYTE *) (header.reserved) = 0
So I disagree with your statements in one point: waveOutWrite() stores a valid pointer first; the pointer only becomes corrupted later from another thread. Probably that's the same thread (mxdmessage) that later tries to free this heap area, but I did not yet find the point where the zero byte is stored.
所以我在一点上不同意你的说法:waveOutWrite() 首先存储一个有效的指针;指针只会在稍后从另一个线程中损坏。可能这是稍后尝试释放此堆区域的同一个线程(mxdmessage),但我还没有找到存储零字节的点。
This does not happen very often, and the same heap area (same address) has successfully been allocated and deallocated before. I'm quite convinced that this is a bug somewhere in the system code.
这种情况并不经常发生,之前已经成功分配和释放了相同的堆区域(相同的地址)。我非常确信这是系统代码中某个地方的错误。
回答by Ana Betts
Use Application Verifier to figure out what's going on, if you do something suspicious, it will catch it much earlier.
使用 Application Verifier 找出发生了什么,如果你做了一些可疑的事情,它会更早地发现它。
回答by dmazzoni
Not sure about this particular problem, but have you considered using a higher-level, cross-platform audio library? There are a lot of quirks with Windows audio programming, and these libraries can save you a lot of headaches.
不确定这个特定问题,但您是否考虑过使用更高级别的跨平台音频库?Windows 音频编程有很多怪癖,这些库可以为您省去很多麻烦。
回答by Stu Mackellar
The first thing that I'd do would be to check the return values from the waveOutX functions. If any of them fail - which isn't unreasonable given the scenario you describe - and you carry on regardless then it isn't surprising that things start to go wrong. My guess would be that waveOutWrite is returning MMSYSERR_NOMEM at some point.
我要做的第一件事是检查 waveOutX 函数的返回值。如果它们中的任何一个失败了——考虑到你描述的场景,这并不是不合理的——并且你继续前进,那么事情开始出错也就不足为奇了。我的猜测是 waveOutWrite 在某个时候返回 MMSYSERR_NOMEM。
回答by Adam Rosenfield
It may be helpful to look at the source code for Wine, although it's possible that Wine has fixed whatever bug there is, and it's also possible Wine has other bugs in it. The relevant files are dlls/winmm/winmm.c, dlls/winmm/lolvldrv.c, and possibly others. Good luck!
查看Wine的源代码可能会有所帮助,尽管Wine可能已经修复了任何存在的错误,也可能 Wine 中存在其他错误。相关文件是 dlls/winmm/winmm.c、dlls/winmm/lolvldrv.c,可能还有其他文件。祝你好运!
回答by pps
What about the fact that you are not allowed to call winmm functions from within callback? MSDN does not mention such restrictions about window messages, but usage of window messages is similar to callback function. Possibly, internally it's implemented as a callback function from the driver and that callback does SendMessage. Internally, waveout has to maintain linked list of headers that were written using waveOutWrite; So, I guess that:
不允许从回调中调用 winmm 函数的事实呢?MSDN 没有提到关于窗口消息的这种限制,但是窗口消息的使用类似于回调函数。可能在内部它是作为驱动程序的回调函数实现的,并且该回调执行 SendMessage。在内部,waveout 必须维护使用 waveOutWrite 写入的标头的链表;所以,我猜是:
hdr->reserved = (Undocumented*)calloc(sizeof(Undocumented));
sets previous/next pointers of the linked list or something like this. If you write more buffers, then if you check the pointers and if any of them point to one another then my guess is most likely correct.
设置链表的上一个/下一个指针或类似的东西。如果你写了更多的缓冲区,那么如果你检查指针,如果它们中的任何一个指向另一个,那么我的猜测很可能是正确的。
Multiple sources on the web mention that you don't need to unprepare/prepare same headers repeatedly. If you comment out Prepare/unprepare header in the original example then it appears to work fine without any problems.
网络上的多个来源提到您不需要重复准备/准备相同的标题。如果您在原始示例中注释掉 Prepare/unprepare 标头,那么它似乎可以正常工作,没有任何问题。
回答by Vadim Galkin
I solved the problem by polling the sound playback and delays:
我通过轮询声音播放和延迟解决了这个问题:
WAVEHDR header = { buffer, sizeof(buffer), 0, 0, 0, 0, 0, 0 };
waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
waveOutWrite(hWaveOut, &header, sizeof(WAVEHDR));
/*
* wait a while for the block to play then start trying
* to unprepare the header. this will fail until the block has
* played.
*/
while (waveOutUnprepareHeader(hWaveOut,&header,sizeof(WAVEHDR)) == WAVERR_STILLPLAYING)
Sleep(100);
waveOutClose(hWaveOut);