为什么 waveOutWrite() 会导致调试堆中出现异常?

2024-03-14

在研究这个问题时,我在网上发现了多次提到以下场景的情况,这些场景总是在编程论坛上成为未解答的问题。我希望在这里发布这篇文章至少可以记录我的发现。

首先,症状:在运行使用 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    

虽然明显的嫌疑是代码中其他地方的堆损坏,但我发现情况并非如此。此外,我能够使用以下代码重现此问题(这是基于对话框的 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;
 }

在任何人对此发表评论之前,是的 - 示例代码会回放未初始化的内存。不要在扬声器开到最大的情况下尝试此操作。

一些调试揭示了以下信息: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 */
}

通常,标头由 wdmaud.dll 的内部函数 waveCompleteHeader() 返回到应用程序。 waveCompleteHeader() 尝试通过调用 GlobalHandle()/GlobalUnlock() 等来释放由 waveOutWrite() 分配的指针。有时,GlobalHandle() 会爆炸,如上所示。

现在,GlobalHandle() 炸弹的原因并不是由于堆损坏,正如我一开始所怀疑的那样 - 这是因为 waveOutWrite() 返回时没有将内部结构中的第一个指针设置为有效指针。我怀疑它在返回之前释放了该指针指向的内存,但我还没有拆解它。

这种情况似乎仅在波形播放系统缓冲区不足时才会发生,这就是我使用单个标头来重现此情况的原因。

此时,我有一个很好的理由来反对这是我的应用程序中的错误 - 毕竟,我的应用程序甚至没有运行。有没有人见过这个?

我在 Windows XP SP2 上看到了这一点。声卡来自SigmaTel,驱动版本为5.10.0.4995。

Notes:

为了防止将来出现混乱,我想指出,认为问题在于使用 malloc()/free() 来管理正在播放的缓冲区的答案是完全错误的。您会注意到,我更改了上面的代码以反映建议,以防止更多的人犯同样的错误 - 这没有什么区别。由waveCompleteHeader() 释放的缓冲区不是包含PCM 数据的缓冲区,释放PCM 缓冲区的责任在于应用程序,并且不要求以任何特定方式分配它。

另外,我确保我使用的所有 waveOut API 调用都不会失败。

我目前假设这要么是 Windows 中的错误,要么是音频驱动程序中的错误。不同意见总是受欢迎的。


现在,GlobalHandle() 的原因 炸弹不是由于堆损坏造成的, 正如我一开始所怀疑的那样 - 这是因为 waveOutWrite() 返回时没有 设置第一个指针 指向有效指针的内部结构。 我怀疑它释放了内存 该指针之前指向的 回来了,不过我还没拆过 还没完成。

我可以在我的系统上使用您的代码重现此内容。我看到了与约翰内斯报道类似的内容。在调用 WaveOutWrite 之后,hdr->reserved 通常保存一个指向已分配内存的指针(其中似乎包含 unicode 格式的波形输出设备名称等)。

但偶尔,从 WaveOutWrite() 返回后,由hdr->reserved设置为 0。这通常是该指针的最低有效字节。其余字节在hdr->reserved没问题,并且它通常指向的内存块仍然被分配且未损坏。

它可能被另一个线程破坏 - 我可以在调用 WaveOutWrite() 后立即使用条件断点捕获更改。并且系统调试断点发生在另一个线程中,而不是消息处理程序中。

但是,如果我使用回调函数而不是 Windows 消息泵,则无法导致系统调试断点发生。 (fdwOpen = CALLBACK_FUNCTION在 WaveOutOpen() 中) 当我这样做时,我的 OnWOMDone 处理程序由另一个线程调用 - 可能是导致损坏的线程。

所以我认为Windows或驱动程序中存在一个错误,但我认为您可以通过使用回调函数而不是Windows消息泵处理WOM_DONE来解决这个问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 waveOutWrite() 会导致调试堆中出现异常? 的相关文章

  • 每个托管线程是否都有自己对应的本机线程?

    我想知道是否在 Net 中创建托管线程 通过调用Thread Start 导致在后台创建一个本机线程 那么托管线程是否有对应的本机线程呢 如果是 当托管线程等待或睡眠时 是否意味着相应的本机线程也在等待或睡眠 是的 NET 线程映射到所有当
  • 为什么 int8_t 和用户通过 cin 输入显示奇怪的结果[重复]

    这个问题在这里已经有答案了 一小段代码让我发疯 但希望你能阻止我跳出窗外 看这里 include
  • 提交后禁用按钮

    当用户提交付款表单并且发布表单的代码导致 Firefox 中出现重复发布时 我试图禁用按钮 去掉代码就不会出现这个问题 在firefox以外的任何浏览器中也不会出现这个问题 知道如何防止双重帖子吗 System Text StringBui
  • 复制 std::function 的成本有多高?

    While std function是可移动的 但在某些情况下不可能或不方便 复制它会受到重大处罚吗 它是否可能取决于捕获变量的大小 如果它是使用 lambda 表达式创建的 它依赖于实现吗 std function通常被实现为值语义 小缓
  • C中的malloc内存分配方案

    我在 C 中尝试使用 malloc 发现 malloc 在分配了一些内存后浪费了一些空间 下面是我用来测试 malloc 的一段代码 include
  • 错误:表达式不产生值

    我尝试将以下 C 代码转换为 VB NET 但在编译代码时出现 表达式不产生值 错误 C Code return Fluently Configure Mappings m gt m FluentMappings AddFromAssemb
  • 复制目录内容

    我想将目录 tmp1 的内容复制到另一个目录 tmp2 tmp1 可能包含文件和其他目录 我想使用C C 复制tmp1的内容 包括模式 如果 tmp1 包含目录树 我想递归复制它们 最简单的解决方案是什么 我找到了一个解决方案来打开目录并读
  • java.io.Serialized 在 C/C++ 中的等价物是什么?

    C C 的等价物是什么java io Serialized https docs oracle com javase 7 docs api java io Serializable html 有对序列化库的引用 用 C 序列化数据结构 ht
  • 为什么 Google 测试会出现段错误?

    我是 Google Test 的新手 正在尝试提供的示例 我的问题是 当我引入失败并设置GTEST BREAK ON FAILURE 1 或使用命令行选项 GTest 将出现段错误 我正在考虑这个例子 https code google c
  • 使用接口有什么好处?

    使用接口有什么用 我听说它用来代替多重继承 并且还可以用它来完成数据隐藏 还有其他优点吗 哪些地方使用了接口 程序员如何识别需要该接口 有什么区别explicit interface implementation and implicit
  • 在 C 中初始化变量

    我知道有时如果你不初始化int 如果打印整数 您将得到一个随机数 但将所有内容初始化为零似乎有点愚蠢 我问这个问题是因为我正在评论我的 C 项目 而且我对缩进非常直接 并且它可以完全编译 90 90 谢谢 Stackoverflow 但我想
  • 为什么 std::strstream 被弃用?

    我最近发现std strstream已被弃用 取而代之的是std stringstream 我已经有一段时间没有使用它了 但它做了我当时需要做的事情 所以很惊讶听到它的弃用 我的问题是为什么做出这个决定 有什么好处std stringstr
  • “接口”类似于 boost::bind 的语义

    我希望能够将 Java 的接口语义与 C 结合起来 起初 我用过boost signal为给定事件回调显式注册的成员函数 这非常有效 但后来我发现一些函数回调池是相关的 因此将它们抽象出来并立即注册所有实例的相关回调是有意义的 但我了解到的
  • 如何在非控制台应用程序中查看 cout 输出?

    输出到调试窗口似乎相当繁琐 我在哪里可以找到cout如果我正在编写非控制台信息 则输出 Like double i a b cout lt lt b lt lt endl I want to check out whether b is z
  • 使用 %d 打印 unsigned long long

    为什么我打印以下内容时得到 1 unsigned long long int largestIntegerInC 18446744073709551615LL printf largestIntegerInC d n largestInte
  • 按 Esc 按键关闭 Ajax Modal 弹出窗口

    我已经使用 Ajax 显示了一个面板弹出窗口 我要做的是当用户按 Esc 键时关闭该窗口 这可能吗 如果有人知道这一点或以前做过这一点 请帮助我 Thanks 通过以下链接 您可以通过按退出按钮轻松关闭窗口 http www codepro
  • 调用堆栈中的“外部代码”是什么意思?

    我在 Visual Studio 中调用一个方法 并尝试通过检查调用堆栈来调试它 其中一些行标记为 外部代码 这到底是什么意思 方法来自 dll已被处决 外部代码 意味着该dll没有可用的调试信息 你能做的就是在Call Stack窗口中单
  • 无法接收 UDP Windows RT

    我正在为 Windows 8 RT 编写一个 Windows Store Metro Modern RT 应用程序 需要在端口 49030 上接收 UDP 数据包 但我似乎无法接收任何数据包 我已按照使用教程进行操作DatagramSock
  • Oracle Data Provider for .NET 不支持 Oracle 19.0.48.0.0

    我们刚刚升级到 Oracle 19c 19 3 0 所有应用程序都停止工作并出现以下错误消息 Oracle Data Provider for NET 不支持 Oracle 19 0 48 0 0 我将 Oracle ManagedData
  • 当我使用 OpenSSL1.1.0g 根据固定的 p 和 g 值创建 Diffie Hellman 密钥协议密钥时,应该执行哪些检查?

    您好 我尝试通过这段代码使用修复 p 和 g 参数来制作 Diffie Hellman Keysanswer https stackoverflow com a 54538811 4706711 include

随机推荐