Windows 10 COM在加载程序锁定下崩溃

时间:2020-08-10 14:54:27

标签: c++ windows core-audio

我偶然发现了Windows 10崩溃,这可能是Windows 10更新到2004年以来的新崩溃。

问题是COM启动时ntdll崩溃。要使用Core Audio,需要COM。

触发它的代码:

    IMMDeviceEnumerator* enumerator = nullptr;
    CoInitializeEx(nullptr, COINIT_MULTITHREADED); // Also with STA
    HRESULT hr = CoCreateInstance( CLSID_MMDeviceEnumerator, 0,  CLSCTX_ALL, IID_IMMDeviceEnumerator,
 (void**)&enumerator);

弹出窗口:

MicTest.exe中0x00007FFE7846D3D1(ntdll.dll)的未处理的异常: LIST_ENTRY已损坏(即两次删除)。

Callstack:

ntdll.dll!LdrpInsertDataTableEntry()    
ntdll.dll!LdrpMapDllWithSectionHandle() 
ntdll.dll!LdrpMapDllNtFileName()    
ntdll.dll!LdrpMapDllFullPath()  
ntdll.dll!LdrpProcessWork() 
ntdll.dll!LdrpLoadDllInternal() 
ntdll.dll!LdrpLoadForwardedDll()    
ntdll.dll!LdrpGetDelayloadExportDll()   
ntdll.dll!LdrpHandleProtectedDelayload()    
ntdll.dll!LdrResolveDelayLoadedAPI()    
combase.dll!00007ffe769ab1c2()  
combase.dll!00007ffe769c56fd()  
combase.dll!00007ffe769b3b27()  
ntdll.dll!RtlRunOnceExecuteOnce()   
KernelBase.dll!InitOnceExecuteOnce()    
combase.dll!00007ffe7696c11f()  
combase.dll!00007ffe7696baf8()  
combase.dll!00007ffe7696b9e6()  
combase.dll!00007ffe76907df2()  
combase.dll!00007ffe76906fce()  
combase.dll!00007ffe76907928()  
combase.dll!00007ffe76907718()  
MicTest.exe!Microphone::Microphone() Line 23    C++
[External Code] 
MicTest.exe!main(int argc, char * * argv) Line 67   C++

那里不是100%可复制的,有时崩溃是在IAudioClient::Initialize之后的几行中,当ntdll在加载器锁定下崩溃时

>   ntdll.dll!LdrpInitializeThread()    
    ntdll.dll!_LdrpInitialize() 
    ntdll.dll!LdrpInitialize()  
    ntdll.dll!LdrInitializeThunk()  

这发生在Windows本身用来加载DLL的新Windows 10线程池线程中。令人反感的代码显示为AudioSes.dll!CAudioClient::CreateRemoteStream(注意:AudioSes.dll是Core Audio,根据Microsoft Symbol Server的符号。)

这是一个已知问题吗?有解决方法吗?

为完整起见,请完整代码:

Microphone::Microphone() 
    
{
    IMMDeviceEnumerator* enumerator = nullptr;
    CoInitializeEx(nullptr, COINIT_MULTITHREADED); // crash near here
    HRESULT hr = CoCreateInstance( CLSID_MMDeviceEnumerator, 0,  CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&enumerator);
    if (!enumerator) throw std::system_error(hr, std::system_category());
    IMMDevice* device = nullptr;
    hr = enumerator->GetDefaultAudioEndpoint(eCapture, eConsole, &device);
    enumerator->Release();
    if (!device) throw std::system_error(hr, std::system_category());
    hr = device->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&client);
    device->Release();
    if (!client) throw std::system_error(hr, std::system_category());

    WAVEFORMATEXTENSIBLE *pwfx = nullptr;
    hr = client->GetMixFormat(reinterpret_cast<WAVEFORMATEX**>(&pwfx));
    if (SUCCEEDED(hr) && pwfx)
    {
        this->span.sampleRate = pwfx->Format.nSamplesPerSec;
        this->blockAlign = pwfx->Format.nBlockAlign;
        // In general, this is NOT pwfx->wBitsPerSample;
        if (pwfx->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
        {
            this->format = fmt_FP32;
        }
        else if (pwfx->SubFormat == KSDATAFORMAT_SUBTYPE_PCM)
        {
            this->format = fmt_PCM;
        }
        else
        {
            // Can't deal with that format, and Core Audio ought to support FP32.
            pwfx->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
            this->format = fmt_FP32;
        }
    }
    // or crash here
    hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, 1000000, 0, &pwfx->Format, NULL);
    if (!SUCCEEDED(hr))
    {
        throw std::system_error(hr, std::system_category());
    }
    CoTaskMemFree(pwfx);

    // Start capture
    hr = client->GetService( IID_IAudioCaptureClient, (void**)&capture);
    client->Start();

1 个答案:

答案 0 :(得分:0)

正如RbMm所指出的,这需要查看程序集。事实证明Windows 10 2004破坏了其PEB_LDR_DATA数据结构。这是每个过程的结构,其中包含重要的数据。特别是,LdrpInitializeThread在获取装载机锁之后立即使用PEB_LDR_DATA::InLoadOrderModuleList。出于不清楚的原因,这可能是空指针。

由于这是在默认线程池中由Windows创建的线程中发生的,因此它与用户代码无关,也不与用户代码同步。崩溃的确切点可能会有所不同-PEB_LDR_DATA结构的损坏似乎更为广泛。

解决方法

虽然这不是CoInitializeEx崩溃的解决方案,但似乎可以通过较小的更改来避免由于PEB_LDR_DATA::InLoadOrderModuleList损坏而导致的第二次崩溃:

  • 不设置缓冲区大小,但让Windows确定缓冲区大小并滚动:
    hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, 0, 0, &pwfx->Format, NULL);
    UINT32 bufSize = 0;
    hr = client->GetBufferSize(&bufSize);
  • 确定何时致电GetBuffer
    auto samplePollRatio = pwfx->Format.nSamplesPerSec / 2666;
    auto nextPoll = std::chrono::system_clock::now() + 
    std::chrono::milliseconds(bufSize / samplePollRatio);
  • 等一下
    std::this_thread::sleep_until(nextPoll);
    auto hr = capture->GetBuffer(&buf, &samples, &flags, nullptr, nullptr);

这是一个合理的模式。 MSDN example也有一个Sleep调用,但是sleep_until在处理过程中的可变延迟方面更强大。 (例如,在第一次调用时,打开WAV文件以保存结果时,您可能会有更多延迟)。但是,通过延迟首次采样的获取,您可以绕开Windows 10中明显的竞争条件。

相关问题