Всем привет, нужна помощь знающих камрадов.
Проблема:
Корраптится куча, приложение падает.
Инфраструктура:
Железо: Виртуальная машина VMware на двух независимых ESXi с якобы живой и неповрежденной памятью
ОС: Microsoft Windows Server 2016 Standard (10.0.14393)
Процесс: Windows-служба x64 под .NET 4.5.2, запущенная под системной учёткой
Отсутствуют антивирусы и подозрительные драйверы.
Падения происходят раз в несколько дней. Традиционно для коррапта кучи — в произвольных местах, при обращении к памяти.
Проблема не поддаётся воспроизведению и стреляет у одного человека из десятков тысяч.
Поковырял WinDbg, куча действительно повреждена:
0:027> !VerifyHeap
object 0000021701b6f728: bad member 0000021701B7C9F8 at 0000021701B6F738
Last good object: 0000021701B6F710.
...
Could not request method table data for object 0000021701B7C9F8 (MethodTable: 39E70380864F9B7C).
Last good object: 0000021701B7C9D0.
Выяснил, что жертвой стали элементы одного из массивов:
> !DumpArray /d 0000021701942910
Name: RootNamespace.FileSession[]
MethodTable: 00007ff87d5d6de0
EEClass: 00007ff8d8498af0
Size: 272(0x110) bytes
Array: Rank 1, Number of elements 31, Type CLASS
Element Methodtable: 00007ff87b627750
[0] 0000021701bd0660
[1] 0000021701bd06a8
[2] 0000021701bd06f0
[3] 0000021701bd0738
> dq 0000021701942910
00000217`01942910 00007ff8`7d5d6de0 00000000`0000001f
00000217`01942920 00000217`01bd0660 00000217`01bd06a8
00000217`01942930 00000217`01bd06f0 00000217`01bd0738
00000217`01942940 00000217`01bd0780 00000217`01bd07c8
00000217`01942950 00000217`01bd0810 00000217`01bd0858
00000217`01942960 00000217`01bd08a0 00000217`01bd0900
00000217`01942970 00000217`01bd0948 00000217`01bd0990
00000217`01942980 00000217`01bd09d8 00000217`01bd0a20
Вначале идёт таблица методов, количество элементов (31 штука) и далее указатели на элементы массива.
Посмотрим что лежит на месте первого элемента:
> !objsize 00000217`01bd0660
sizeof(0000021701bd0660) = 72 (0x48) bytes (RootNamespace.FileSession)
> dq 00000217`01bd0660 L9
00000217`01bd0660 00007ff8`7b627750 00000000`00000001
00000217`01bd0670 486b85bd`e6c79316 c3d8f969`c90b70ba
00000217`01bd0680 4b059f8e`1e4ddd83 a9c00cf5`7d3ca0a7
00000217`01bd0690 46de7a50`833a6808 6dfea71f`5f3810b8
00000217`01bd06a0 00000000`00000000
Теперь плохой массив:
!DumpArray /d 0000021701b6f728
Name: RootNamespace.FileSession[]
MethodTable: 00007ff87d5d6de0
EEClass: 00007ff8d8498af0
Size: 272(0x110) bytes
Array: Rank 1, Number of elements 31, Type CLASS
Element Methodtable: 00007ff87b627750
[0] 0000021701b7c9f8
[1] 0000021701b7ca40
[2] 0000021701b7ca88
[3] 0000021701b7cad0
И его первый элемент:
> dq 0000021701b7c9f8 L9
00000217`01b7c9f8 39e70380`864f9b7e 64017283`9c955ef7
00000217`01b7ca08 e7694b13`d6b9417a 7049840e`f4aaffbd
00000217`01b7ca18 37a6b685`02f4fade da40d413`1f15d547
00000217`01b7ca28 f47180d7`d905900a 67649a05`b2eb5deb
00000217`01b7ca38 0d708b24`098c06b4
По адресу 00000217`01b7c9f8 лежит не элемент массива, а обыкновенный мусор.
Но если мы отмотаем память немного назад, то обнаружим...
00000217`01b7c9e0 00000000`00000000 00000002`00000002
00000217`01b7c9f0 07745b33`00000000 39e70380`864f9b7e
00000217`01b7ca00 64017283`9c955ef7 e7694b13`d6b9417a
00000217`01b7ca10 7049840e`f4aaffbd 37a6b685`02f4fade
00000217`01b7ca20 da40d413`1f15d547 f47180d7`d905900a
00000217`01b7ca30 67649a05`b2eb5deb 0d708b24`098c06b4
00000217`01b7ca40 ce6ca3df`a30a02f1 9d9f32ae`eaa97bb7
00000217`01b7ca50 36b0ac8a`91d534b1 f58dbd9a`2c6c8055
00000217`01b7ca60 c2b5f5c5`d2d982c5 d87fcea8`953f75a3
00000217`01b7ca70 a1f50c94`313b216a 2651d93a`7f1b2eb0
00000217`01b7ca80 b42d9e5f`265a92d4 d8bbde2c`7c5b1071
00000217`01b7ca90 efae2f4d`07e993e6 998584a8`44f82ef3
00000217`01b7caa0 85a5e61d`7d80cae7 1703c807`bf3c62a4
00000217`01b7cab0 2a1677d9`30847e64 75d48062`37544d6c
00000217`01b7cac0 ee2e1bcf`c2f920aa 84fa15c7`817649aa
00000217`01b7cad0 fa891630`c36247b3 cfb37b15`a2c20424
00000217`01b7cae0 588cf96f`8d9a9b55 5abbb836`66dab3e4
00000217`01b7caf0 dd245fd8`cff527e0 305d965e`88702ad4
00000217`01b7cb00 bc2c5fa8`60c0a7c4 da78741f`3b160725
00000217`01b7cb10 10e18e91`03b009e6 289665d2`a2f45daa
00000217`01b7cb20 987ff2be`b8bca1be 45a2969e`922a2e06
00000217`01b7cb30 67face47`2fa1cee6 005fd79e`81aa3a4f
00000217`01b7cb40 30813582`50286f3b 64624dfb`1a442845
00000217`01b7cb50 66f8af79`e3d568d1 c45acd5d`a0e8a5a6
...что мусор начинается имнно с того места, где раньше находился первый элемент массива.
Меня терзают смутные сомнения, что память, выделенная этому объекту была помечена, как незанятая и просто была переиспользована другим компонентом без каких-либо плясок с unsafe и выходом за границы буфера. Или GC попытался дефрагментировать память, и сделать ему это не удалось.
В связи с этим, у меня несколько вопросов:
1) На забугорных ресурсах кому-то помогает отключение параллелинга в GC
<configuration>
<runtime>
<gcConcurrent enabled="false"/>
</runtime>
</configuration>
Если это не эффект плацебо, значит существуют какие-то гонки между потоками сборщика мусора, которые могут приводить к коррапту кучи? С чем они связаны? От чего зависят? ОС, окружение, железо?
2) Может ли на это как-то повлиять наличие или отсутствие флажка gcServer?
3) Как можно посмотреть — в какие захваченные регионы входит данный фрагмент памяти, кто его аллоцировал, кто держит (поимо !gcroot)?
Подозрительным является то, что "мусор" начинается ровно по границе массива. >_>
Ещё один камень в сторону GC. Другой дамп:
> !VerifyHeap
Could not request method table data for object 000001C88304B5E8 (MethodTable: C4C1934C7B7ABE70).
Last good object: 000001C88304B5C0.
> !objsize 000001C88304B5C0
sizeof(000001c88304b5c0) = 40 (0x28) bytes (System.String)
> db 000001C88304B5C0 L 40
000001c8`8304b5c0 98 de a8 d8 f8 7f 00 00-07 00 00 00 6d 00 6d 00 ............m.m.
000001c8`8304b5d0 2d 00 33 00 30 00 33 00-31 00 00 00 00 00 00 00 -.3.0.3.1.......
000001c8`8304b5e0 00 00 00 00 00 00 00 00-70 be 7a 7b 4c 93 c1 c4 ........p.z{L...
000001c8`8304b5f0 d3 5e e6 47 9c 83 18 2d-c4 8e d0 93 2d 49 4e 72 .^.G...-....-INr
> !gcroot 000001C88304B5C0
Found 0 unique roots (run '!GCRoot -all' to see all roots).
> !gcroot 000001C88304B5E8
Found 0 unique roots (run '!GCRoot -all' to see all roots).
Ready for finalization 0 objects (000001c8d0102cb0->000001c8d0102cb0)
Ссылок на эти объекты нет, в очереди финализатора их нет, зато есть мусор в чужой памяти.