Heap corruption - кто виноват?
От: LWhisper  
Дата: 18.08.17 17:06
Оценка:
Всем привет, нужна помощь знающих камрадов.

Проблема:
Корраптится куча, приложение падает.

Инфраструктура:
Железо: Виртуальная машина 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)


Ссылок на эти объекты нет, в очереди финализатора их нет, зато есть мусор в чужой памяти.
Отредактировано 18.08.2017 17:42 LWhisper . Предыдущая версия . Еще …
Отредактировано 18.08.2017 17:07 LWhisper . Предыдущая версия .
.net windbg debug memory gc heap
Re: Heap corruption - кто виноват?
От: Sharov Россия  
Дата: 18.08.17 17:31
Оценка:
Здравствуйте, LWhisper, Вы писали:

Если никаких манипуляций с памятью через указатели не делается, то может просто на данной машине битая память? Т.е. проблема в железе.
Кодом людям нужно помогать!
Re: Heap corruption - кто виноват?
От: Слава  
Дата: 18.08.17 17:34
Оценка:
Здравствуйте, LWhisper, Вы писали:

LW>Корраптится куча, приложение падает.


Пусть прогонят физический тест памяти. У них там ECC или самосбор?
Re[2]: Heap corruption - кто виноват?
От: LWhisper  
Дата: 18.08.17 17:44
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Если никаких манипуляций с памятью через указатели не делается, то может просто на данной машине битая память? Т.е. проблема в железе.


Как я написал выше, это виртуальная машина, которую тестировали на двух разных железных серверах.
Вероятность того, что память битая на обоих крайне мала. Скорее у самих серверов бага в механизме работы с памятью.
Re[2]: Heap corruption - кто виноват?
От: LWhisper  
Дата: 18.08.17 17:45
Оценка:
Здравствуйте, Слава, Вы писали:

С>Пусть прогонят физический тест памяти. У них там ECC или самосбор?


Как я написал выше, это виртуальная машина, которую тестировали на двух разных железных серверах.
Вероятность того, что память битая на обоих крайне мала. Скорее у самих серверов бага в механизме работы с памятью. Но это не объясняет, почему проблема возникает только в нашем приложении.
Re[3]: Heap corruption - кто виноват?
От: Sharov Россия  
Дата: 18.08.17 17:50
Оценка:
Здравствуйте, LWhisper, Вы писали:



LW>Как я написал выше, это виртуальная машина, которую тестировали на двух разных железных серверах.

LW>Вероятность того, что память битая на обоих крайне мала. Скорее у самих серверов бага в механизме работы с памятью.

Виноват, невнимательно прочитал. Думал что на одной и той железке гоняли.
Кодом людям нужно помогать!
Re[3]: Heap corruption - кто виноват?
От: Aquilaware  
Дата: 19.08.17 02:24
Оценка:
Здравствуйте, LWhisper, Вы писали:

LW>Как я написал выше, это виртуальная машина, которую тестировали на двух разных железных серверах.


К слову, недавно в VMWare закрыли несколько подобных багов которые редко, но приводили к спонтанному разрушению памяти хоста и гостевой ОС.

Если вы не используете PInvoke или ручную работу с памятью, то проблем быть не должно.
Re: Heap corruption - кто виноват?
От: VladCore  
Дата: 19.08.17 05:04
Оценка: +1
Здравствуйте, LWhisper, Вы писали:

LW>Всем привет, нужна помощь знающих камрадов.


LW>Проблема:

LW>Корраптится куча, приложение падает.

Пробежался я по ответам. Мне ни один не понравился.

Кучу обычно портит либо кривой unmanaged, unsafe код или кривые Marshal-вызовы. Некий код "срёт" не в ту память и портит чужие данные а именно кучу. Пытался вспомнить короткий синоним к выражению "срёт в память" и пока не вспомнил

Это самое очевидное объяснение, если на разном железе одинаковое поведение.

Маловероятное объяснение — в винде можно грузить любую unmanaged dll во все процессы. Вот она и жжёт не по детски. Если у тебя один образ ВМ, то может и вдруг.
Re: Heap corruption - кто виноват?
От: okon  
Дата: 19.08.17 10:37
Оценка:
Что значит кораптица куча ?
У процесса есть 32/64 бита виртуального простраства, он же может туда клась что угодно и как угодно ?
”Жить стало лучше... но противнее. Люди которые ставят точку после слова лучше становятся сторонниками Путина, наши же сторонники делают акцент на слове противнее ( ложь, воровство, лицемерие, вражда )." (с) Борис Немцов
Re[4]: Heap corruption - кто виноват?
От: LWhisper  
Дата: 21.08.17 08:26
Оценка:
Здравствуйте, Aquilaware, Вы писали:

A>К слову, недавно в VMWare закрыли несколько подобных багов которые редко, но приводили к спонтанному разрушению памяти хоста и гостевой ОС.

A>Если вы не используете PInvoke или ручную работу с памятью, то проблем быть не должно.

Используем, конечно.
Спасибо, посмотрим. Если под рукой есть ссылки — делись.
Re[2]: Heap corruption - кто виноват?
От: LWhisper  
Дата: 21.08.17 08:31
Оценка:
Здравствуйте, VladCore, Вы писали:

VC>Маловероятное объяснение — в винде можно грузить любую unmanaged dll во все процессы. Вот она и жжёт не по детски. Если у тебя один образ ВМ, то может и вдруг.


Данное объяснение более вероятно, как и повреждение файлов .NET Framework, так как проблема аффектит одного человека из сотен тысяч.
Re: Heap corruption - кто виноват?
От: rm822 Россия  
Дата: 21.08.17 09:09
Оценка: 5 (1)
Здравствуйте, LWhisper, Вы писали:

LW>Всем привет, нужна помощь знающих камрадов.


LW>Проблема:

LW>Корраптится куча, приложение падает.
Поделюсь мыслями на этот счет
1. Не стоит слишком доверять дампам и !VerifyHeap
потоки замораживаются в произвольный момент, GC может компактить кучу в это время, часть из них может что-то выделять, объекты могут быть не созданы до конца
стоит убедиться что ты не гоняешься за химерой

2. если есть подозрения на Unmanaged компоненты, как вариант есть
gcUnmanagedToManaged MDA
gcUnmanagedToManaged MDA
он сильно тормозит, и только для дебага

3. Отловить все вызовы Marshsal.Copy через рефлектор или типа того. Нифига они не безопасные вопреки устоявшемуся мнению.
4. Если убитый объект находится в 0 поколении, скорее всего он убит вот только-только, надо смотреть стеки потоков, м.б. они еще не ушли от точки разрушения
5. еще вариант AppDomain.FirstChanceException, ловить accessviolation
+<configuration>
<runtime>
<legacyCorruptedStateExceptionsPolicy enabled="true" />
</runtime>
</configuration>
Re[2]: Heap corruption - кто виноват?
От: LWhisper  
Дата: 21.08.17 09:21
Оценка:
O>Что значит кораптица куча ?
O>У процесса есть 32/64 бита виртуального простраства, он же может туда клась что угодно и как угодно ?

В вопросе всё подробно описано.
Данные записываются поверх заголовков объектов, перетирая друг друга, как если бы данная область памяти считалась свободной.
Это может быть либо следствие прямого доступа к памяти и ошибке в алгоритме, косяка в системных запчастях, кривых драйверах, антивирусах или косяки железного сервера.
Re[3]: Heap corruption - кто виноват?
От: Sharov Россия  
Дата: 21.08.17 09:43
Оценка:
Здравствуйте, LWhisper, Вы писали:

LW>Здравствуйте, VladCore, Вы писали:


VC>>Маловероятное объяснение — в винде можно грузить любую unmanaged dll во все процессы. Вот она и жжёт не по детски. Если у тебя один образ ВМ, то может и вдруг.


LW>Данное объяснение более вероятно, как и повреждение файлов .NET Framework, так как проблема аффектит одного человека из сотен тысяч.


Прощу прощения еще раз, но с железом у данного человека все нормально? Разные виртуалки гоняли у данного клиента?
Кодом людям нужно помогать!
Re[4]: Heap corruption - кто виноват?
От: LWhisper  
Дата: 21.08.17 10:37
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Прощу прощения еще раз, но с железом у данного человека все нормально? Разные виртуалки гоняли у данного клиента?


У клиента несколько железных серверов, на которых гоняли одну и ту же склонированную виртуалку. Вероятно, проблема либо в системом софте, либо в самой виртуалке или приложении, но не в железе.
Re[2]: Heap corruption - кто виноват?
От: LWhisper  
Дата: 21.08.17 10:41
Оценка:
Здравствуйте, rm822, Вы писали:

R>1. Не стоит слишком доверять дампам и !VerifyHeap

R> потоки замораживаются в произвольный момент, GC может компактить кучу в это время, часть из них может что-то выделять, объекты могут быть не созданы до конца
R> стоит убедиться что ты не гоняешься за химерой
Именно в этом и пытаюсь убедиться.

R>2. если есть подозрения на Unmanaged компоненты, как вариант есть

R> gcUnmanagedToManaged MDA
R> gcUnmanagedToManaged MDA
R> он сильно тормозит, и только для дебага
Спасибо за наводку. Как я понимаю, это спровоцирует креш сразу после такого вызова?

R>3. Отловить все вызовы Marshsal.Copy через рефлектор или типа того. Нифига они не безопасные вопреки устоявшемуся мнению.

Проблема в том, что конфирмится у одного единственного человека из сотен тысяч.

R>4. Если убитый объект находится в 0 поколении, скорее всего он убит вот только-только, надо смотреть стеки потоков, м.б. они еще не ушли от точки разрушения

Увы, не нашёл.

R>5. еще вариант AppDomain.FirstChanceException, ловить accessviolation

R>+<configuration>
R> <runtime>
R> <legacyCorruptedStateExceptionsPolicy enabled="true" />
R> </runtime>
R></configuration>
К сожалению, это не просто досрочно убитые объекты, а объекты, на которые ещё есть ссылки. Поэтому мы отвалимся в будущем при работе с ними. Единственный вариант — выгрузить домен и запустить повторно, чего делать не хочется, пока проблема не будет ясна.
Re[3]: Heap corruption - кто виноват?
От: kov_serg Россия  
Дата: 21.08.17 12:59
Оценка:
R>>3. Отловить все вызовы Marshsal.Copy через рефлектор или типа того. Нифига они не безопасные вопреки устоявшемуся мнению.
LW>Проблема в том, что конфирмится у одного единственного человека из сотен тысяч.
А что за реальное железо у этого человека? Реальный проц какой?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.