{"id":1058,"date":"2013-08-24T12:08:32","date_gmt":"2013-08-24T10:08:32","guid":{"rendered":"http:\/\/blog.m-ri.de\/?p=1058"},"modified":"2013-08-22T12:49:58","modified_gmt":"2013-08-22T10:49:58","slug":"windows-7-playsound-und-die-vermisste-pruefung-auf-speicherlecks","status":"publish","type":"post","link":"http:\/\/blog.m-ri.de\/index.php\/2013\/08\/24\/windows-7-playsound-und-die-vermisste-pruefung-auf-speicherlecks\/","title":{"rendered":"Windows 7, PlaySound und die vermisste Pr\u00fcfung auf Speicherlecks"},"content":{"rendered":"<p>Bei der Entwicklung von neuen Funktionen in einem Modul bekam ich irgendwann einen <em>ASSERT.<\/em> Diesen <em>ASSERT<\/em> hatte ich in einem Cache eingebaut. Der <em>ASSERT<\/em> sprang an, wenn bei Programmende der Cache nicht sauber aufger\u00e4umt wurde. Also eigentlich in der Entwicklungsphase nichts ungew\u00f6hnliches. Irgendwo wurde also eine Funktion zum Aufr\u00e4umen nicht aufgerufen.<\/p>\n<p>Aber was mich in diesem Moment extrem stutzig machte, war, dass in der Debugausgabe meines <em>VisualStudios<\/em> keine Memory Leaks angezeigt wurden \u2757 Das machte irritierte mich nun schon sehr. Erster Verdacht.\u00a0Evtl. hat ja jemand die Leakpr\u00fcfung f\u00fcr den Cache ausgeschaltet (siehe <em>AfxEnableMemoryTracking<\/em>). Aber eine kurze Pr\u00fcfung ergab, dass dem nicht so ist.<\/p>\n<p>Also den Test noch mal ausgef\u00fchrt. Diesmal wieder der <em>ASSERT<\/em> und zus\u00e4tzlich der Dump. &#8222;Oha&#8220; dachte ich &#8222;Hier ist aber was richtig faul!&#8220;<\/p>\n<p>Nachdem ich immer wieder andere Testszenarien verwendet habe, erschienen mal die Leaks und mal erschienen sie nicht. Und nach einiger Zeit kaum ich dahinter, dass immer wenn <em>PlaySound<\/em> in meiner Debug Version der Anwendung verwendet wurde, kein Leak-Check erfolgte. Wurde <em>PlaySound<\/em> nicht verwendet war alles gut und die Leaks wurden ausgegeben.<\/p>\n<p>Jetzt ging es ans eingemachte. Schnell isolierte ich folgende DLLs die zus\u00e4tzlich beim ersten <em>PlaySound<\/em> geladen wurden.<\/p>\n<pre lang=\"Text\">'xyz.exe': Loaded 'C:\\Windows\\SysWOW64\\MMDevAPI.dll', Symbols loaded (source information stripped).\r\n'xyz.exe': Loaded 'C:\\Windows\\SysWOW64\\propsys.dll', Symbols loaded (source information stripped).\r\n'xyz.exe': Loaded 'C:\\Windows\\SysWOW64\\wdmaud.drv', Symbols loaded (source information stripped).\r\n'xyz.exe': Loaded 'C:\\Windows\\SysWOW64\\ksuser.dll', Symbols loaded (source information stripped).\r\n'xyz.exe': Loaded 'C:\\Windows\\SysWOW64\\avrt.dll', Symbols loaded (source information stripped).\r\n'xyz.exe': Loaded 'C:\\Windows\\SysWOW64\\setupapi.dll', Symbols loaded (source information stripped).\r\n'xyz.exe': Loaded 'C:\\Windows\\SysWOW64\\cfgmgr32.dll', Symbols loaded (source information stripped).\r\n'xyz.exe': Loaded 'C:\\Windows\\SysWOW64\\devobj.dll', Symbols loaded (source information stripped).\r\n'xyz.exe': Loaded 'C:\\Windows\\SysWOW64\\AudioSes.dll', Symbols loaded (source information stripped).\r\n'xyz.exe': Loaded 'C:\\Windows\\SysWOW64\\msacm32.drv', Symbols loaded (source information stripped).\r\n'xyz.exe': Loaded 'C:\\Windows\\SysWOW64\\msacm32.dll', Symbols loaded (source information stripped).\r\n'xyz.exe': Loaded 'C:\\Windows\\SysWOW64\\midimap.dll', Symbols loaded (source information stripped).<\/pre>\n<p>Und nach etwas weiter debuggen kam ich dahinter, dass der <em>DllMain<\/em> Code von setupapi.dll beim <em>DETACH_PROCESS<\/em> den Prozess sofort terminiert und nicht alle DLLs einen <em>DETACH_PROCESS<\/em> Aufruf erhalten. Aber der Code f\u00fcr die Leak-Detection der <em>MFC<\/em> liegt in der <em>MFCx.DLL<\/em> und wird durch eine statische Variable ausgel\u00f6st, die beim Entladen der <em>MFCx.DLL<\/em> dann zur Ausgabe der Speicherlecks f\u00fchrt. (Siehe <em>PROCESS_LOCAL(_AFX_DEBUG_STATE, afxDebugState)<\/em> und <em>AfxDiagnosticInit<\/em>).<\/p>\n<p>Eine genauere Analyse des Stacktraces ergab folgendes Bild:<\/p>\n<pre lang=\"Text\">ntdll.dll!_ZwTerminateProcess@8()  + 0x5 bytes\t\r\nntdll.dll!_RtlpWaitOnCriticalSection@8()  + 0x1d38f bytes\t\r\nntdll.dll!_RtlEnterCriticalSection@4()  + 0x16a38 bytes\t\r\nsetupapi.dll!_pSetupInitGlobalFlags@4()  + 0x1fc bytes\t\r\nsetupapi.dll!_pSetupGetGlobalFlags@0()  + 0xe2 bytes\t\r\nsetupapi.dll!_FlushWVTCache@0()  + 0x2f bytes\t\r\nsetupapi.dll!_DestroyDeviceInfoSet@8()  + 0x3f bytes\t\r\nsetupapi.dll!_SetupDiDestroyDeviceInfoList@4()  + 0x44 bytes\t\r\nMMDevAPI.dll!CDeviceEnumerator::FinalRelease()  + 0x78 bytes\t\r\nMMDevAPI.dll!ATL::CComObjectCached::~CComObjectCached()  + 0x3c bytes\t\r\nMMDevAPI.dll!ATL::CComObjectCached::`scalar deleting destructor'()  + 0xd bytes\t\r\nMMDevAPI.dll!ATL::CComObjectCached::Release()  + 0xf0c6 bytes\t\r\nMMDevAPI.dll!ATL::CComClassFactorySingleton::~CComClassFactorySingleton()  + 0x18 bytes\t\r\nMMDevAPI.dll!ATL::CComObjectNoLock&lt;ATL::CComClassFactorySingleton &gt;::`scalar deleting destructor'()  + 0x1a bytes\t\r\nMMDevAPI.dll!ATL::CComObjectNoLock&lt;ATL::CComClassFactorySingleton &gt;::Release()  + 0xe0e3 bytes\t\r\nMMDevAPI.dll!ATL::CAtlComModule::Term()  + 0x27 bytes\t\r\nMMDevAPI.dll!__CRT_INIT@12()  + 0x26e6 bytes\t\r\nMMDevAPI.dll!__CRT_INIT@12()  + 0x2588 bytes\t\r\nntdll.dll!_LdrpCallInitRoutine@16()  + 0x14 bytes\t\r\nntdll.dll!_LdrShutdownProcess@0()  + 0x141 bytes\t\r\nntdll.dll!_RtlExitUserProcess@4()  + 0x74 bytes\t\r\nkernel32.dll!75eb7a0d() \t\r\nmsvcr100d.dll!__crtExitProcess(int status=0)  Line 709\tC\r\nmsvcr100d.dll!doexit(int code=0, int quick=0, int retcaller=0)  Line 621 + 0x9 bytes\tC\r\nmsvcr100d.dll!exit(int code=0)  Line 393 + 0xd bytes\tC\r\nxyz.exe!__tmainCRTStartup()  Line 568\tC<\/pre>\n<p>Ursache war, dass <em>MMDevAPI.DLL<\/em> in seinem <em>DllMain<\/em> Code ausf\u00fchrt in der setupapi.dll, die aber bereits den <em>DETACH_PROCESS<\/em> abgearbeitet hat. Das grunds\u00e4tzliche Problem wird hier in diesem Blog Artikel geschildert: <a href=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2005\/05\/23\/421024.aspx\">http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2005\/05\/23\/421024.aspx<br \/>\n<\/a>Mit eigenen Worten: Die <em>MMDevAPI.dll<\/em> ruft Funktionen in einer DLL auf, die bereits alle Ihren Speicher und Ressourcen freigegeben hat. Und wie es hier im Code so aussieht, versucht die <em>SetupAPI.DLL<\/em> wieder Ressourcen zu akquirieren, die eben bereits schon freigegeben wurden, weil eine DLL sie erneut benutzt.<\/p>\n<p>Die Folge ein &#8222;Crash&#8220; in <em>DllMain,<\/em> die der Windows-Lader aber abf\u00e4ngt und sofort mit einer Terminierung des Prozesses ahndet. D.h. nun aber, dass nicht alle <em>DLLs,<\/em> die noch einen <em>DllMain<\/em> Aufruf mit <em>DETACH_PROCESS<\/em> erhalten m\u00fcssten,\u00a0\u00a0auch an die Reihe kommen.<\/p>\n<p>Etwas weitere Recherche ergab, dass dieses Problem auch in den MSDN Foren bereits diskutiert wurde inkl. einer m\u00f6glichen L\u00f6sung.<br \/>\n<a href=\"http:\/\/social.msdn.microsoft.com\/Forums\/vstudio\/en-US\/8cb1847d-3218-4610-9cb8-6905bd255ff5\/no-dllprocessdetach-after-calling-playsound-on-windows-7-64bit\">http:\/\/social.msdn.microsoft.com\/Forums\/vstudio\/en-US\/8cb1847d-3218-4610-9cb8-6905bd255ff5\/no-dllprocessdetach-after-calling-playsound-on-windows-7-64bit<\/a><\/p>\n<p>Die L\u00f6sung ist erstaunlich einfach: Wenn vor der Benutzung von <em>PlaySound<\/em> explizit die <em>SetupAPI.DLL<\/em> geladen wird, dann verl\u00e4uft der Rest der Deinitialisierung vollkommen normal. SetupAPI.DLL wird nicht entladen, weil die DLL durch den <em>LoadLibrary<\/em> Aufruf die DLL im Speicher sperrt. <em>MMDevAPI.DLL<\/em> kann erfolgreich selbst aufr\u00e4umen und der Code l\u00e4uft nicht mehr ins Nirvana.<\/p>\n<p>Hier handelt sich offensichtlich um einen Bug in <em>Windows 7<\/em> (<em>Windows 8<\/em> konnte ich dies bzgl. nicht testen).<br \/>\nDieser Bug kann nat\u00fcrlich auch empfindlichere Probleme mit sich bringen, wenn Ressourcen betroffen sind, die nicht automatisch mit Prozessende freigegeben werden und wenn diese Ressourcen ausschlie\u00dflich in der <em>DllMain<\/em> bei einem\u00a0<em>DETACH_PROCESS<\/em> behandelt werden.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Bei der Entwicklung von neuen Funktionen in einem Modul bekam ich irgendwann einen ASSERT. Diesen ASSERT hatte ich in einem Cache eingebaut. Der ASSERT sprang an, wenn bei Programmende der Cache nicht sauber aufger\u00e4umt wurde. Also eigentlich in der Entwicklungsphase nichts ungew\u00f6hnliches. Irgendwo wurde also eine Funktion zum Aufr\u00e4umen nicht aufgerufen. Aber was mich in &hellip; <a href=\"http:\/\/blog.m-ri.de\/index.php\/2013\/08\/24\/windows-7-playsound-und-die-vermisste-pruefung-auf-speicherlecks\/\" class=\"more-link\"><span class=\"screen-reader-text\">\u201eWindows 7, PlaySound und die vermisste Pr\u00fcfung auf Speicherlecks\u201c <\/span>weiterlesen<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[30,11,4,17,2],"tags":[38,136,61,186],"class_list":["post-1058","post","type-post","status-publish","format-standard","hentry","category-c","category-debugging","category-mfc","category-vista-2","category-windows-api","tag-debuggen","tag-qualitaetssicherung","tag-winapi","tag-windows-7"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"http:\/\/blog.m-ri.de\/index.php\/wp-json\/wp\/v2\/posts\/1058","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/blog.m-ri.de\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.m-ri.de\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.m-ri.de\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.m-ri.de\/index.php\/wp-json\/wp\/v2\/comments?post=1058"}],"version-history":[{"count":1,"href":"http:\/\/blog.m-ri.de\/index.php\/wp-json\/wp\/v2\/posts\/1058\/revisions"}],"predecessor-version":[{"id":1059,"href":"http:\/\/blog.m-ri.de\/index.php\/wp-json\/wp\/v2\/posts\/1058\/revisions\/1059"}],"wp:attachment":[{"href":"http:\/\/blog.m-ri.de\/index.php\/wp-json\/wp\/v2\/media?parent=1058"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.m-ri.de\/index.php\/wp-json\/wp\/v2\/categories?post=1058"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.m-ri.de\/index.php\/wp-json\/wp\/v2\/tags?post=1058"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}