- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
今天是🐏的第四天,头终于不巨疼了,写文章已经没什么问题,赶紧爬起来写.
这个月初有位朋友找到我,说他的程序出现了CPU爆高,让我帮忙看下怎么回事,简单分析了下有两点比较有意思.
这是一个安全生产的信息管理平台,第一次听说,我的格局小了.
这是一个经典的 CPU 爆高问题,过往虽有分析,但没有刨根问底,刚好这一篇就来问一下底吧.
话不多说,我们上 WinDbg 说话.
别人说爆高不算,我们得拿数据说话不是,验证命令就是 !tp .
0:085> !tp
CPU utilization: 100%
Worker Thread: Total: 40 Running: 26 Idle: 6 MaxLimit: 32767 MinLimit: 8
Work Request in Queue: 0
--------------------------------------
Number of Timers: 0
--------------------------------------
Completion Port Thread:Total: 1 Free: 1 MaxFree: 16 CurrentLimit: 1 MaxLimit: 1000 MinLimit: 8
从卦中看果然是被打满了,接下来可以用 ~*e !clrstack 观察各个线程都在做什么,稍微一观察就会发现有很多的线程卡在 FindEntry() 方法上,截图如下:
从图中可以看到,有 25 个线程都停在 FindEntry() 之上,如果你的经验比较丰富的话,我相信你马上就知道这是多线程环境下使用了非线程安全集合 Dictionary 造成的死循环,把 CPU 直接打爆.
按以往套路到这里就结束了,今天我们一定要刨到底.
要知道死循环的成因,那就一定要从 FindEntry 上入手.
private int FindEntry(TKey key)
{
if (key == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
if (buckets != null)
{
int num = comparer.GetHashCode(key) & 0x7FFFFFFF;
for (int num2 = buckets[num % buckets.Length]; num2 >= 0; num2 = entries[num2].next)
{
if (entries[num2].hashCode == num && comparer.Equals(entries[num2].key, key))
{
return num2;
}
}
}
return -1;
}
仔细观察上面的代码,如果真有死循环肯定是在 for 中出不来,如果是真的出在 for 上,那问题自然在 next 指针上.
关于 Dictionary 的内部布局和解析 可以参见我的 高级调试训练营 ,这里我们就不细说了.
那是不是出在 next 指针上呢? 我们来剖析下方法上下文.
为了方便观察,先切到 85 号线程.
0:085> ~85s
mscorlib_ni!System.Collections.Generic.Dictionary<string,F2.xxx.ORM.SqlEntity>.FindEntry+0x8f:
00007ff8`5f128ccf 488b4e10 mov rcx,qword ptr [rsi+10h] ds:0000017f`39c07d00=0000017eb9ee00c0
0:085> !clrstack
OS Thread Id: 0x4124 (85)
Child SP IP Call Site
0000007354ebcc70 00007ff85f128ccf System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].FindEntry(System.__Canon) [f:\dd\ndp\clr\src\BCL\system\collections\generic\dictionary.cs @ 305]
接下来把 Dictionary 中的 Entry[] 中的 next 给展示出来,可以用 !mdso 命令.
0:085> !mdso
Thread 85:
Location Object Type
------------------------------------------------------------
RCX: 0000017eb9ee00c0 System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[xx]][]
RSI: 0000017f39c07cf0 System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[xxx.xxx]]
0:085> !mdt -e:2 0000017eb9ee00c0
0000017eb9ee00c0 (System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[xxx.xxx]][], Elements: 3, ElementMT=00007ff816cedc18)
[0] (System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[F2.xxx]]) VALTYPE (MT=00007ff816cedc18, ADDR=0000017eb9ee00d0)
hashCode:0x0 (System.Int32)
next:0x0 (System.Int32)
key:NULL (System.__Canon)
value:NULL (System.__Canon)
[1] (System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[F2.xxx]]) VALTYPE (MT=00007ff816cedc18, ADDR=0000017eb9ee00e8)
hashCode:0x5aba4760 (System.Int32)
next:0xffffffff (System.Int32)
key:0000017f39c0ab50 (System.String) Length=20, String="xxxMessage_Select"
value:0000017f39c0b5d0 (xxx.xxx.ORM.SqlEntity)
[2] (System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[F2.xxx]]) VALTYPE (MT=00007ff816cedc18, ADDR=0000017eb9ee0100)
hashCode:0x65b6e27b (System.Int32)
next:0x1 (System.Int32)
key:0000017f39c09d58 (System.String) Length=20, String="xxxMessage_Insert"
value:0000017f39c0ba50 (xxx.xxx.ORM.SqlEntity)
从卦中看也蛮奇葩的,只有三个元素的 Dictionary 还能死循环。。。如果你仔细观察会发现 [0] 项是一种有损状态,value 没值不说, next:0x0 可是有大问题的,它会永远指向自己,因为 next 是指向 hash 挂链中的下一个节点的数组下标,画个图大概是这样.
接下来我们验证下是不是入口参数不幸进入了 [0] 号坑,然后在这个坑中永远指向自己呢?要想寻找答案,只需要在 FindEntry 的汇编代码中找到 int num = comparer.GetHashCode(key) & 0x7FFFFFFF; 中的 num 值,看它是不是 0 即可.
0:085> !U /d 00007ff85f128ccf
preJIT generated code
System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].FindEntry(System.__Canon)
Begin 00007ff85f128c40, size 130. Cold region begin 00007ff85ff07ff0, size 11
...
f:\dd\ndp\clr\src\BCL\system\collections\generic\dictionary.cs @ 303:
00007ff8`5f128c6f 488b5e18 mov rbx,qword ptr [rsi+18h]
00007ff8`5f128c73 488b0e mov rcx,qword ptr [rsi]
00007ff8`5f128c76 488b5130 mov rdx,qword ptr [rcx+30h]
00007ff8`5f128c7a 488b2a mov rbp,qword ptr [rdx]
00007ff8`5f128c7d 4c8b5d18 mov r11,qword ptr [rbp+18h]
00007ff8`5f128c81 4d85db test r11,r11
00007ff8`5f128c84 750f jne mscorlib_ni!System.Collections.Generic.Dictionary<string,xxx.SqlEntity>.FindEntry+0x55 (00007ff8`5f128c95)
00007ff8`5f128c86 488d154d2f1800 lea rdx,[mscorlib_ni+0x68bbda (00007ff8`5f2abbda)]
00007ff8`5f128c8d e8ce44f3ff call mscorlib_ni+0x43d160 (00007ff8`5f05d160) (mscorlib_ni)
00007ff8`5f128c92 4c8bd8 mov r11,rax
00007ff8`5f128c95 488bcb mov rcx,rbx
00007ff8`5f128c98 488bd7 mov rdx,rdi
00007ff8`5f128c9b 3909 cmp dword ptr [rcx],ecx
00007ff8`5f128c9d 41ff13 call qword ptr [r11]
00007ff8`5f128ca0 8bd8 mov ebx,eax
00007ff8`5f128ca2 81e3ffffff7f and ebx,7FFFFFFFh
...
0:085> ? ebx
Evaluate expression: 957083499 = 00000000`390bef6b
0:085> ? 0n957083499 % 0n3
Evaluate expression: 0 = 00000000`00000000
从汇编代码中分析得出,num 是放在 ebx 寄存器上,此时 num=957083499 ,再 %3 之后就是 0 号坑,大家再结合源代码,你会发现这里永远都不会退出,永远都是指向自己,自然就是死循环了.
前段时间在整理课件时发现在 .NET6 中不再傻傻的死循环,而是在尝试 entries.Length 次之后还得不到结束的话,强制抛出异常,代码如下:
internal ref TValue FindValue(TKey key)
{
uint hashCode2 = (uint)comparer.GetHashCode(key);
int bucket2 = GetBucket(hashCode2);
Entry[] entries2 = _entries;
uint num2 = 0u;
bucket2--;
while ((uint)bucket2 < (uint)entries2.Length)
{
reference = ref entries2[bucket2];
if (reference.hashCode != hashCode2 || !comparer.Equals(reference.key, key))
{
bucket2 = reference.next;
num2++;
if (num2 <= (uint)entries2.Length)
{
continue;
}
goto IL_0171;
}
goto IL_0176;
}
return ref Unsafe.NullRef<TValue>();
IL_0176:
return ref reference.value;
IL_0171:
ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
goto IL_0176;
}
可能是 .NET团队 被这样的问题咨询烦了,干脆抛一个异常得了。。.
多线程环境下使用线程不安全集合,问题虽然很小白,但还是有很多朋友栽在这上面,值得反思哈,借这一次机会进一步解释下死循环形成的内部机理.
最后此篇关于记一次.NET某安全生产信息系统CPU爆高分析的文章就讲到这里了,如果你想了解更多关于记一次.NET某安全生产信息系统CPU爆高分析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在学习 Spring 安全性,但我对它的灵活性感到困惑.. 我知道我可以通过在标签中定义规则来保护网址 然后我看到有一个@secure 注释可以保护方法。 然后还有其他注释来保护域(或 POJO)
假设有一个 key 加密 key 位于内存中并且未写入文件或数据库... byte[] kek = new byte[32]; secureRandom.nextBytes(kek); byte[]
我有 Spring Security 3.2.0 RC1 的问题 我正在使用标签来连接我 这表示“方法‘setF
我正在创建一个使用 Node Js 服务器 API 的 Flutter 应用程序。对于授权,我决定将 JWT 与私钥/公钥一起使用。服务器和移动客户端之间的通信使用 HTTPS。 Flutter 应用
在过去的几年里,我一直在使用范围从 Raphael.js 的 javascript 库。至 D3 ,我已经为自己的教育操纵了来自网络各地的动画。我已经从各种 git 存储库下载了 js 脚本,例如 s
在 python 中实现身份验证的好方法是什么?已经存在的东西也很好。我需要它通过不受信任的网络连接进行身份验证。它不需要太高级,只要足以安全地获取通用密码即可。我查看了 ssl 模块。但那个模块让我
我正在尝试学习“如何在 Hadoop 中实现 Kerberos?”我已经看过这个文档 https://issues.apache.org/jira/browse/HADOOP-4487我还了解了基本的
我有一个带有 apache2、php、mysql 的生产服务器。我现在只有一个站点 (mysite.com) 作为虚拟主机。我想把 phpmyadmin、webalizer 和 webmin 放在那里
前些天在网上看到防火墙软件OPNsense,对其有了兴趣,以前写过一个其前面的一个软件M0n0wall( 关于m0n0wa
我在 Spring Boot 和 oauth2(由 Google 提供)上编写了 rest 后端,在 "/login" 上自动重定向。除了 web 的 oauth 之外,我还想在移动后端进行 Fire
我想调用类 Foo,它的构造函数中有抽象类 Base。我希望能够从派生自 Base 的 Derived 调用 Foo 并使用 Derived覆盖方法而不是 Base 的方法。 我只能按照指示使用原始指
如何提高 session 的安全性? $this->session->userdata('userid') 我一直在为我的 ajax 调用扔掉这个小坏蛋。有些情况我没有。然后我想,使用 DOM 中的
我目前正在为某些人提供程序集编译服务。他们可以在在线编辑器中输入汇编代码并进行编译。然后编译它时,代码通过ajax请求发送到我的服务器,编译并返回程序的输出。 但是,我想知道我可以做些什么来防止对服务
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引起辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the he
目前,我通过将 session 中的 key 与 MySQl 数据库中的相同 key 相匹配来验证用户 session 。我使用随机数重新生成 session ,该随机数在每个页面加载时都受 MD5
Microsoft 模式与实践团队提供了一个很棒的 pdf,称为:“构建安全的 asp.net 应用程序”。 microsoft pdf 由于它是为 .Net 1.0 编写的,所以现在有点旧了。有谁知
在 Lua 中,通常会使用 math.random 生成随机值和/或字符串。 & math.randomseed , 其中 os.time用于 math.randomseed . 然而,这种方法有一个
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用资料或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
我们有一个严重依赖 Ajax 的应用程序。确保对服务器端脚本的请求不是通过独立程序而是通过坐在浏览器上的实际用户的好方法是什么 最佳答案 真的没有。 通过浏览器发送的任何请求都可以由独立程序伪造。 归
我正在寻找使用 WebSockets 与我们的服务器通信来实现 web (angular) 和 iPhone 应用程序。在过去使用 HTTP 请求时,我们使用请求数据、url、时间戳等的哈希值来验证和
我是一名优秀的程序员,十分优秀!