Возможная ошибка ресурса вызова JIT.NET JIT?

Я преследовал причину прерывистого сбоя в одной из наших служб.NET из-за внутренней ошибки в.NET Runtime (код выхода 0x80131506). Услуга, о которой идет речь, не выполняет какие-либо виды операций, которые обычно виноваты в таких ошибках (небезопасный код, PInvoke и т.д.). Я попытался отключить параллельный GC, как описано в KB2679415, а также переключиться на сервер GC, но прерывистые сбои сохраняются. Проблема проявляется в.NET 4.7.2 и более ранних версиях при компиляции в режиме отладки.

Служба широко использует старую версию NHibernate (2.0.1), и когда я рассматривал аварийные дампы в отладчике, всегда есть код NHibernate в стоп-косте при возникновении ошибки, хотя сам NHibernate - это все управляемый код, поэтому не должно быть способно вызвать такой крах.

Мне удалось воспроизвести сбой при отладчике, и с включенным журналом стресса GC и проверкой кучи, и, хотя он, похоже, указывает на проблему в JIT/GC, я не уверен, что правильно интерпретирую вывод.

Глядя на поток, на котором происходит сбой, в этом случае он встречается в clr!JIT_Stelem_Ref:

clr!JIT_Stelem_Ref+0x18: cmp     r9,qword ptr [r8] ds:aaaaaaaa'aaaaaaaa=????????????????

В этом случае строка 0xaa, по-видимому, является результатом включения HeapVerify, что приводит к заполнению собранных областей памяти GC, по-видимому, для облегчения идентификации, и предполагает, что каким-то образом мы все еще имеем ссылку на старое местоположение собранного/перемещенного объекта,

Отслеживание назад в стеке, есть много записей 0xaaaaaaaaaaaaaaaa, однако они перестают появляться в методе, который находился в верхней части стека вызовов, когда произошел самый последний GC, который в этом случае был NHibernate.Loader.Loader.GetRow() в соответствии с журналом напряжений GC для последнего GC на этой теме:

(Примечание: я изменил порядок !dumplog строк из вывода SOS ' !dumplog для упрощения чтения):

2404 12445.672380360 : 'GC'GCROOTS'         Starting scan of Thread 000000001EF4DED0 ID = 20 {
2404 12445.672380963 : 'GCROOTS'            Scanning ExplicitFrame 000000001E6ED3B8 AssocMethod = 0000000000000000 frameVTable = 000007FEF365B640 (clr!RedirectedThreadFrame::'vftable')
2404 12445.672386397 : 'GCROOTS'            Scanning Frameless method 000007FE93F43460 (NHibernate.Loader.Loader.GetRow(System.Data.IDataReader, NHibernate.Persister.Entity.ILoadable[], NHibernate.Engine.EntityKey[], System.Object, NHibernate.Engine.EntityKey, NHibernate.LockMode[], System.Collections.IList, NHibernate.Engine.ISessionImplementor)) ControlPC = 000007FE945E3095
2404 12445.672388208 : 'GC'GCROOTS'             GC Root 000000001E6ED4C0 RELOCATED 000000003B1A7708 -> 000000003AC89F08  MT = 000007FE93DDF5C8 (...)
2404 12445.672388510 : 'GC'GCROOTS'             GC Root 000000001E6ED4D8 RELOCATED 000000003B1A73A0 -> 000000003AC89D00  MT = 000007FEF1FD6EA8 (System.Object[])
2404 12445.672388510 : 'GC'GCROOTS'             GC Root 000000001E6ED4E8 RELOCATED 000000003B1A7358 -> 000000003AC89CB8  MT = 000007FE9491D7C8 (NHibernate.Engine.EntityKey)
2404 12445.672388510 : 'GC'GCROOTS'             GC Root 000000001E6ED4F8 RELOCATED 000000003B1A73A0 -> 000000003AC89D00  MT = 000007FEF1FD6EA8 (System.Object[])

Область стека для этого метода выглядит следующим образом:

00000000'1e6ed470 000000003b1a7358 ✕
00000000'1e6ed478 000000000291e3d0 
00000000'1e6ed480 0000000000000000 
00000000'1e6ed488 0000000000000000 
00000000'1e6ed490 000000000662a900 
00000000'1e6ed498 0000000006523c80 
00000000'1e6ed4a0 0000000000000000 
00000000'1e6ed4a8 0000000000000000 
00000000'1e6ed4b0 0000000000000000 
00000000'1e6ed4b8 0000000000000000 
00000000'1e6ed4c0 000000003ac89f08 ✔
00000000'1e6ed4c8 0000000000000000 
00000000'1e6ed4d0 0000000006524248 
00000000'1e6ed4d8 000000003ac89d00 ✔
00000000'1e6ed4e0 0000000000000000 
00000000'1e6ed4e8 000000003ac89cb8 ✔
00000000'1e6ed4f0 0000000000000000 
00000000'1e6ed4f8 000000003ac89d00 ✔
00000000'1e6ed500 0000000100000000 
00000000'1e6ed508 0000000c0000000b 
00000000'1e6ed510 0000000006621660 
00000000'1e6ed518 000000001e6ed690 
00000000'1e6ed520 000000001e6ed6a0

Я указал 4 записи, упомянутые в журнале стресса GC, как перенесенные, которые были правильно обновлены с их новыми адресами, однако первая запись стека (000000003b1a7358 - NHibernate.Engine.EntityKey), хотя она является одним из перемещенных объектов, не обновляется с новым адресом. Конечно, это было бы совершенно нормально, если бы это больше не использовалось, но на самом деле оно должно передаваться как параметр для вызова NHibernate.Loader.Loader.InstanceNotYetLoaded().

InstanceNotYetLoaded() принимает 9 параметров (плюс this), и я отметил, где каждая из них загружается в стек/регистр в следующем списке сборок. Я также включил соответствующий вывод из SOS ' !gcinfo поскольку он относится к каждому из параметров в стеке:

Param Address              Instruction                        GC Info
      000007fe'945e3071    mov     r9,qword ptr [rbp-38h]
  P4> 000007fe'945e3075    mov     qword ptr [rsp+20h],r9
      000007fe'945e307a    mov     r9d,dword ptr [rbp-18h]    +sp+20
      000007fe'945e307e    mov     rcx,qword ptr [rbp+40h]
      000007fe'945e3082    cmp     r9,qword ptr [rcx+8]
      000007fe'945e3086    jb      000007fe'945e308d
      000007fe'945e3088    call    clr!JIT_RngChkFail
      000007fe'945e308d    lea     rcx,[rcx+r9*8+10h]         -sp+20
      000007fe'945e3092    mov     r9,qword ptr [rcx]
-- GC Occurred Here --
  P5> 000007fe'945e3095    mov     qword ptr [rsp+28h],r9
      000007fe'945e309a    mov     r9,qword ptr [rbp+38h]     +sp+28
  P6> 000007fe'945e309e    mov     qword ptr [rsp+30h],r9
      000007fe'945e30a3    mov     r9,qword ptr [rbp+30h]     +sp+30
  P7> 000007fe'945e30a7    mov     qword ptr [rsp+38h],r9
      000007fe'945e30ac    mov     r9,qword ptr [rbp+48h]     +sp+38
  P8> 000007fe'945e30b0    mov     qword ptr [rsp+40h],r9
      000007fe'945e30b5    mov     r9,qword ptr [rbp+50h]     +sp+40
  P9> 000007fe'945e30b9    mov     qword ptr [rsp+48h],r9
      000007fe'945e30be    mov     r9d,dword ptr [rbp-18h]    +sp+48
      000007fe'945e30c2    mov     rcx,qword ptr [rbp+20h]
      000007fe'945e30c6    cmp     r9,qword ptr [rcx+8]
      000007fe'945e30ca    jb      000007fe'945e30d1
      000007fe'945e30cc    call    clr!JIT_RngChkFail
      000007fe'945e30d1    lea     rcx,[rcx+r9*8+10h]         -sp+48 -sp+40 -sp+38 -sp+30 -sp+28
  P3> 000007fe'945e30d6    mov     r9,qword ptr [rcx]
this> 000007fe'945e30d9    mov     rcx,qword ptr [rbp+10h]
  P1> 000007fe'945e30dd    mov     rdx,qword ptr [rbp+18h]
  P2> 000007fe'945e30e1    mov     r8d,dword ptr [rbp-18h]
      000007fe'945e30e5    call    InstanceNotYetLoaded(...)

GC непосредственно перед 000007fe945e3095 произошел в 000007fe945e3095, который после загрузки параметра 4 в стек (на 000007fe945e3075), но также после того, как эта запись стека стала мертвой (в 000007fe945e308d) в соответствии с информацией GC, которая объясняет, почему Фаза перераспределения GC не обновляла эту ссылку.

Похоже, что GC Info для параметров 5-9 также неправильно маркирует их как слишком ранние, и, возможно, в обоих случаях они помечены как мертвые сразу после того, что выглядит как проверка диапазона массива.

Это для меня выглядит как ошибка JIT, когда время жизни этих параметров стека неправильно отслеживается. Правильно ли этот анализ, если да, где лучше всего сообщить, если. Если это не ошибка JIT, что мне не хватает, что может объяснить эти неожиданные сбои на чисто управляемом коде?

Редактировать:

Я считаю, что следующий фрагмент воспроизведет проблему, по крайней мере, до создания плохой информации GC в режиме отладки.

public void Repro(int p1, object p2, object p3, object p4, object[] p5)
{
    // Incorrect GC Info generated for this call
    ReproHelper(p1, p2, p3, p4, p5[p1]);
}

public void ReproHelper(int p1, object p2, object p3, object p4, object p5)
{
    Console.WriteLine(p1);
    Console.WriteLine(p2);
    Console.WriteLine(p3);
    Console.WriteLine(p4);
    Console.WriteLine(p5);
}

В сущности, должен быть метод вызова метода, который:

  • Требуется не менее двух параметров, которые должны быть переданы в стек (т.е. не менее 5 параметров для метода экземпляра).
  • 2- й параметр, переданный в стек (параметр 5), должен быть результатом доступа к массиву.

Когда эти критерии соблюдены, 4- й параметр загружается в стек для вызова, а запись стека правильно помечена как содержащая ссылку. Однако при определении значения параметра 5 выполняется проверка диапазона диапазона массива, и после этого запись стека для параметра 4 помечена как мертвая.

Если GC-GC происходит после проверки диапазона, но до фактического вызова, и GC дает объект, который передавался как перемещаемый параметр 4, когда метод возобновляется, вызов передает старый (недопустимый) адрес в параметр 4, а не новый.

Ответ 1

Хотя это не ответ на проблему, я рассматриваю ее как таковую, потому что я подтверждаю, что она является ошибкой и должна быть исправлена командой.NET.

При запуске вашего фрагмента на.NET Framework 4.7.1 (clrjit.dll версии 4.7.2xxx) была создана надлежащая GCInfo (и +sp+20 фактически записана только перед ReproHelper):

00007ffb'99450630 55              push    rbp
00007ffb'99450631 4883ec40        sub     rsp,40h
00000003 is a safepoint: 
00007ffb'99450635 488d6c2440      lea     rbp,[rsp+40h]
00007ffb'9945063a 33c0            xor     eax,eax
00007ffb'9945063c 488945f8        mov     qword ptr [rbp-8],rax
00007ffb'99450640 48894d10        mov     qword ptr [rbp+10h],rcx
00007ffb'99450644 895518          mov     dword ptr [rbp+18h],edx
00007ffb'99450647 4c894520        mov     qword ptr [rbp+20h],r8
00007ffb'9945064b 4c894d28        mov     qword ptr [rbp+28h],r9
interruptible
+rbp+28 +rbp+20 +rbp+10 +rbp-8
00007ffb'9945064f 833d3a3fefff00  cmp     dword ptr [00007ffb'99344590],0
00007ffb'99450656 7405            je      00007ffb'9945065d
00007ffb'99450658 e863eaab5f      call    clr!JIT_DbgIsJustMyCode (00007ffb'f8f0f0c0)
00007ffb'9945065d 90              nop
00007ffb'9945065e 8b5518          mov     edx,dword ptr [rbp+18h]
00007ffb'99450661 4c8b4538        mov     r8,qword ptr [rbp+38h]
+r8
00007ffb'99450665 413b5008        cmp     edx,dword ptr [r8+8]
00007ffb'99450669 7205            jb      00007ffb'99450670
-rbp-8
00007ffb'9945066b e8f015ac5f      call    clr!JIT_RngChkFail (00007ffb'f8f11c60)
-r8
00007ffb'99450670 488b5538        mov     rdx,qword ptr [rbp+38h]
+rdx
00007ffb'99450674 448b4518        mov     r8d,dword ptr [rbp+18h]
00007ffb'99450678 4d63c0          movsxd  r8,r8d
00007ffb'9945067b 4a8b54c210      mov     rdx,qword ptr [rdx+r8*8+10h]
00007ffb'99450680 488955f8        mov     qword ptr [rbp-8],rdx
+rbp-8
00007ffb'99450684 488b55f8        mov     rdx,qword ptr [rbp-8]
00007ffb'99450688 4889542428      mov     qword ptr [rsp+28h],rdx
+sp+28
00007ffb'9945068d 8b5518          mov     edx,dword ptr [rbp+18h]
-rdx
00007ffb'99450690 4c8b4520        mov     r8,qword ptr [rbp+20h]
+r8
00007ffb'99450694 4c8b4d28        mov     r9,qword ptr [rbp+28h]
+r9
00007ffb'99450698 488b4d30        mov     rcx,qword ptr [rbp+30h]
+rcx
00007ffb'9945069c 48894c2420      mov     qword ptr [rsp+20h],rcx
+sp+20
00007ffb'994506a1 488b4d10        mov     rcx,qword ptr [rbp+10h]
-rbp-8

Но после обновления до.NET Framework 4.7.2 (clrjit.dll версии 4.7.3062), он уже не является правильным (+sp+20 записывается до проверки диапазона массива массива, правильно установлен, но из внезапного неустановленного впоследствии, тогда как все еще используется в вызове ReproHelper):

00007ffe'62290630 55              push    rbp
00007ffe'62290631 4883ec30        sub     rsp,30h
00007ffe'62290635 488d6c2430      lea     rbp,[rsp+30h]
00000007 is a safepoint: 
00007ffe'6229063a 48894d10        mov     qword ptr [rbp+10h],rcx
00007ffe'6229063e 895518          mov     dword ptr [rbp+18h],edx
00007ffe'62290641 4c894520        mov     qword ptr [rbp+20h],r8
00007ffe'62290645 4c894d28        mov     qword ptr [rbp+28h],r9
interruptible
+rbp+28 +rbp+20 +rbp+10
00007ffe'62290649 833d483fefff00  cmp     dword ptr [00007ffe'62184598],0
00007ffe'62290650 7405            je      00007ffe'62290657
00007ffe'62290652 e869f7aa5f      call    clr!TranslateSecurityAttributes+0x857b0 (00007ffe'c1d3fdc0) (JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE)
00007ffe'62290657 90              nop
00007ffe'62290658 488b4d30        mov     rcx,qword ptr [rbp+30h]    
+rcx
00007ffe'6229065c 48894c2420      mov     qword ptr [rsp+20h],rcx
+sp+20
00007ffe'62290661 8b4d18          mov     ecx,dword ptr [rbp+18h]
-rcx
00007ffe'62290664 488b5538        mov     rdx,qword ptr [rbp+38h]
+rdx
00007ffe'62290668 483b4a08        cmp     rcx,qword ptr [rdx+8]
00007ffe'6229066c 7205            jb      00007ffe'62290673
00007ffe'6229066e e8ed22ab5f      call    clr!TranslateSecurityAttributes+0x88350 (00007ffe'c1d42960) (JitHelp: CORINFO_HELP_RNGCHKFAIL)
-sp+20
00007ffe'62290673 488d54ca10      lea     rdx,[rdx+rcx*8+10h]
-rdx +rdx(interior)
00007ffe'62290678 488b0a          mov     rcx,qword ptr [rdx]
+rcx
00007ffe'6229067b 48894c2428      mov     qword ptr [rsp+28h],rcx
+sp+28
00007ffe'62290680 488b4d10        mov     rcx,qword ptr [rbp+10h]
00007ffe'62290684 8b5518          mov     edx,dword ptr [rbp+18h]
-rdx(interior)
00007ffe'62290687 4c8b4520        mov     r8,qword ptr [rbp+20h]
+r8
00007ffe'6229068b 4c8b4d28        mov     r9,qword ptr [rbp+28h]
+r9
00007ffe'6229068f e804faffff      call    00007ffe'62290098 (GCInfoBug.Bug.ReproHelper(Int32, System.Object, System.Object, System.Object, System.Object), mdToken: 0000000006000004)
-sp+28 -r9 -r8 -rcx
00007ffe'62290694 90              nop
00007ffe'62290695 90              nop
not interruptible
-rbp+28 -rbp+20 -rbp+10
00007ffe'62290696 488d6500        lea     rsp,[rbp]
00007ffe'6229069a 5d              pop     rbp
00007ffe'6229069b c3              ret