草庐IT

c# - 可能是.NET JIT调用参数生存期错误?

coder 2023-07-13 原文

我一直在寻找由于.NET运行时内部错误(退出代码0x80131506)而导致我们的.NET服务间歇性崩溃的原因。有问题的服务不会执行通常应归咎于此类错误的任何类型的操作(不安全代码,PInvoke等)。我曾尝试按照KB2679415中所述禁用并发GC以及切换到服务器GC,但是间歇性崩溃仍然存在。当以 Debug模式进行编译时,此问题在.NET 4.7.2和更早版本中显现。

该服务广泛使用了旧版本的NHibernate(2.0.1),当我在调试器中检查了故障转储时,发生错误时,调用堆栈中总会有NHibernate代码,尽管NHibernate本身就是托管代码,因此应该不会导致这种崩溃。

我设法在调试器下并且启用了GC Stress Log和Heap Verification的情况下重现了崩溃,尽管它似乎指出了JIT/GC中的问题,但我不确定我是否正确解释了输出。

查看发生崩溃的线程,在这种情况下,它发生在clr!JIT_Stelem_Ref:

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

在这种情况下,0xaa的字符串似乎是启用HeapVerify的结果,这可能导致GC填充了收集的内存区域,大概是为了便于识别,并建议我们仍然以某种方式引用了收集/重定位的旧位置目的。

回溯到堆栈中,有很多0xaaaaaaaaaaaaaaaa条目,但是当最近的GC发生时,这些条目不再出现在调用堆栈顶部的方法中,根据GC的压力日志,在这种情况下为 NHibernate.Loader.Loader.GetRow() 。此线程上的最新GC:

(注意:为了方便阅读,我从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

我已指出GC压力日志中提到的4个条目已重定位,这些条目已正确地更新了它们的新地址,但是第一个堆栈条目(000000003b1a7358-NHibernate.Engine.EntityKey)虽然是重定位的对象之一,但不是用新地址更新。如果不再使用它,那当然将是完全正常的,但是实际上它将作为参数传递给call NHibernate.Loader.Loader.InstanceNotYetLoaded()
InstanceNotYetLoaded()接受9个参数(加上this),我在下面的程序 list 上标记了每个参数在何处装入堆栈/寄存器。我还包括了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处,这是在将参数4加载到堆栈上之后(在000007fe945e3075处),而且还在此堆栈条目变为无效之后(在000007fe945e308d处),根据GC Info,这将解释为什么GC重定位阶段没有更新此引用。

看起来参数5-9的GC信息还错误地将它们标记为过早失效,也许可以说,在这两种情况下,在看起来像数组索引范围检查之后,它们都立即被标记为失效。

在我看来,这似乎是一个JIT错误,这些堆栈参数的生存期未正确跟踪。这种分析是否正确,如果可以,最好在哪里报告。如果这不是JIT错误,那么我到底缺少什么可以解释纯托管代码上的这些意外崩溃?

编辑:

我相信以下代码片段将重现此问题,至少在 Debug模式中生成不良的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);
}

本质上,必须有一个方法调用,该方法应:
  • 要求至少2个参数在堆栈上传递(即,实例方法至少5个参数)。
  • 在堆栈上传递的第二个参数(参数5)必须是数组访问的结果。

  • 当满足这些条件时,第4个参数将加载到调用堆栈中,并且堆栈条目正确标记为包含引用。但是,在确定参数5的值时,将进行数组索引范围检查,并且在此之后将参数4的堆栈条目标记为无效。

    如果GC在范围检查之后但在实际调用发生之前发生,并且GC导致通过重定位作为参数4传递的对象,则在该方法恢复时,调用会将旧(无效)地址传递给参数4,而不是新的。

    最佳答案

    尽管它不能解决问题,但我将其视为是错误,因此应将其视为.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
    

    关于c# - 可能是.NET JIT调用参数生存期错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51991070/

    有关c# - 可能是.NET JIT调用参数生存期错误?的更多相关文章

    1. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

      大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

    2. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

      我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

    3. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

      exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

    4. ruby - RSpec - 使用测试替身作为 block 参数 - 2

      我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

    5. ruby - 如何在 Ruby 中拆分参数字符串 Bash 样式? - 2

      我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"

    6. ruby - 检查方法参数的类型 - 2

      我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

    7. ruby-on-rails - 在默认方法参数中使用 .reverse_merge 或 .merge - 2

      两者都可以defsetup(options={})options.reverse_merge:size=>25,:velocity=>10end和defsetup(options={}){:size=>25,:velocity=>10}.merge(options)end在方法的参数中分配默认值。问题是:哪个更好?您更愿意使用哪一个?在性能、代码可读性或其他方面有什么不同吗?编辑:我无意中添加了bang(!)...并不是要询问nobang方法与bang方法之间的区别 最佳答案 我倾向于使用reverse_merge方法:option

    8. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

      我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

    9. ruby-on-rails - 如何在 Rails View 上显示错误消息? - 2

      我是rails的新手,想在form字段上应用验证。myviewsnew.html.erb.....模拟.rbclassSimulation{:in=>1..25,:message=>'Therowmustbebetween1and25'}end模拟Controller.rbclassSimulationsController我想检查模型类中row字段的整数范围,如果不在范围内则返回错误信息。我可以检查上面代码的范围,但无法返回错误消息提前致谢 最佳答案 关键是您使用的是模型表单,一种显示ActiveRecord模型实例属性的表单。c

    10. 使用 ACL 调用 upload_file 时出现 Ruby S3 "Access Denied"错误 - 2

      我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file

    随机推荐