前几天公众号里有位兄弟看了几篇文章之后,也准备用windbg试试看,结果这一配就花了好几天,(づ╥﹏╥)づ,我想也有很多跃跃欲试的朋友在配置的时候肯定会遇到这样和那样的问题,所以我觉得有必要整理一下,让大家少走弯路。
现在安装windbg越来越麻烦,还要安装Windows 10 SDK,很多人就栽在这里,其实大家可以直接在网上找一键打包的windbg 6.0版本即可,才30多M,调生产调本地都很方便,顺带还可以练练SOS命令。
云盘:https://pan.baidu.com/s/1VqXVIGVHxAZVPNds1525Jg 提取码:mahg
外网:http://www.33lc.com/soft/96743.html
解压打开会有一个x64和x86文件夹,很显然,32位的程序用x86下的windbg调试,64位的程序用x64的windbg调试,如下图:
我比较喜欢用64bit程序,所以这里使用64位的windbg。
符号其实就是pdb文件,我们在debug模式下编译项目都会看到这个,它的作用会对dll进行打标,这样在调试时通过pdb就能看到局部变量,全局变量,行号等等其他信息,在FCL类库中的pdb文件就放在微软的公有服务器上,SRV*C:\mysymbols*http://msdl.microsoft.com/download/symbols。
很多时候大家都是事后调试,所以需要在生产上抓一个dump文件,为了将dump文件逆向到clr上的运行时状态,你必须要寻找到当时运行程序clr版本,同时也要找到对应clr版本的sos.dll,他们通常是在一起的,sos 就是 你 和 clr交互的渠道,很多人都卡在寻找正确版本的sos和clr版本上。。。如果不清楚,我可以画张图。
有了这个前置基础,接下来就可以在windows和centos上进行配置实践了。。。
为了演示,我先上一段简单的代码:
static void Main(string[] args)
{
var info="hello world!";
Console.WriteLine(info);
Console.ReadLine();
}
在netcore中,clr的名字变成了 coreclr.dll,路径: C:\Program Files\dotnet\shared\Microsoft.NETCore.App.1.3
netcore3.0开始,sos就没有放在版本号文件下了,详见 SOS_README.md 内容。
SOS and other diagnostic tools now ship of band and work with any version of the .NET Core runtime.
SOS has moved to the diagnostics repo here: https://github.com/dotnet/diagnostics.git.
Instructions to install SOS: https://github.com/dotnet/diagnostics#installing-sos.
看了上面文档,大概意思就是说老版本的windbg,需要通过小工具dotnet-sos 自己生成一个sos.dll,那就按照文档来吧
PS C:\WINDOWS\system32> dotnet tool install -g dotnet-sos
You can invoke the tool using the following command: dotnet-sos
Tool 'dotnet-sos' (version '3.1.122203') was successfully installed.
PS C:\WINDOWS\system32> dotnet-sos install
Installing SOS to C:\Users\hxc\.dotnet\sos from C:\Users\hxc\.dotnet\tools\.store\dotnet-sos\3.1.122203\dotnet-sos\3.1.122203\tools\netcoreapp2.1\any\win-x64
Installing over existing installation...
Creating installation directory...
Copying files...
Execute '.load C:\Users\hxc\.dotnet\sos\sos.dll' to load SOS in your Windows debugger.
Cleaning up...
SOS install succeeded
PS C:\WINDOWS\system32>
仔细看输出,sos.dll 已经生成好了,接下来在任务管理器中生成一个dump文件,然后使用 .load 命令把 coreclr 和 sos 加载进去即可。
.load C:\Users\hxc\.dotnet\sos\sos.dll
.load C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.3\coreclr.dll
最后我们抓一下 info 变量在堆上的分布。
0:000> ~0s
ntdll!ZwReadFile+0x14:
00007ff8`3228aa64 c3 ret
0:000> !clrstack -l
OS Thread Id: 0x41d4 (0)
000000246097EA40 00007FFF89C50F97 Error: Fail to initialize CoreCLR 80131022
ConsoleApp5.Program.Main(System.String[])
LOCALS:
0x000000246097EA68=0x0000021d8141aba8
0:000> !do 0x0000021d8141aba8
Name: System.String
MethodTable: 00007fff89cd1e18
EEClass: 00007fff89cc2128
Size: 46(0x2e) bytes
File: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.3\System.Private.CoreLib.dll
String: hello world!
Fields:
MT Field Offset Type VT Attr Value Name
00007fff89c1b1e8 4000242 8 System.Int32 1 instance 12 _stringLength
00007fff89c18000 4000243 c System.Char 1 instance 68 _firstChar
00007fff89cd1e18 4000244 110 System.String 0 static 0000021d81411360 Empty
好了,windows上的netcore调试就这么简单,希望这些配置能节省您的时间。
framework程序比netcore配置要方便的多,不需要自己去生成sos了,如下代码所示:
64位程序加载路径
.load C:\Windows\Microsoft.NET\Framework64\v4.0.30319\sos.dll
.load C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
32位程序加载路径
.load C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll
.load C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
首先要明白,对于linux内核windbg就失效了,那怎么调试呢? 有两种方式。
这个工具的地方在于,sos和clr都不需要你配置,直接使用它生成dump,然后直接调试,方便至极,下面看看怎么安装,开两个terminal,如下代码:
terminal 1:
[root@10-25-198-96 data]# dotnet build
[root@10-25-198-96 netcoreapp3.1]# dotnet data.dll
hello world
terminal 2:
[root@10-25-198-96 cs2]# ps -ef | grep dotnet
root 31555 31247 0 22:28 pts/0 00:00:00 dotnet cs2.dll
root 32112 31995 0 22:29 pts/2 00:00:00 grep --color=auto dotnet
[root@10-25-198-96 cs2]# dotnet tool install -g dotnet-dump
You can invoke the tool using the following command: dotnet-dump
Tool 'dotnet-dump' (version '3.1.122203') was successfully installed.
[root@10-25-198-96 cs2]# export PATH=$PATH:$HOME/.dotnet/tools
[root@10-25-198-96 cs2]# dotnet-dump collect --process-id 31555
Writing full to /cs2/core_20200508_223204
Complete
可以看到dump文件已经好了 /cs2/core_20200508_223204 ,接下来用 dotnet-dump 对dump文件调试。
[root@10-25-198-96 cs2]# dotnet-dump analyze /cs2/core_20200508_223204
Loading core dump: /cs2/core_20200508_223204 ...
Ready to process analysis commands. Type 'help' to list available commands or 'help [command]' to get detailed help on a command.
Type 'quit' or 'exit' to exit the session.
> clrstack -l
OS Thread Id: 0x7b43 (0)
Child SP IP Call Site
00007FFDFCABF2D0 00007fb0397af7fd [InlinedCallFrame: 00007ffdfcabf2d0] Interop+Sys.ReadStdin(Byte*, Int32)
00007FFDFCABF2D0 00007fafbebbb4db [InlinedCallFrame: 00007ffdfcabf2d0] Interop+Sys.ReadStdin(Byte*, Int32)
00007FFDFCABF2C0 00007FAFBEBBB4DB ILStubClass.IL_STUB_PInvoke(Byte*, Int32)
00007FFDFCABF9D0 00007FAFBECF844D System.Console.ReadLine()
00007FFDFCABF9E0 00007FAFBEBB037D cs2.Program.Main(System.String[]) [/cs2/Program.cs @ 13]
LOCALS:
0x00007FFDFCABF9F0=0x00007faf980081d8
00007FFDFCABFD08 00007fb037fc0f7f [GCFrame: 00007ffdfcabfd08]
00007FFDFCAC01F0 00007fb037fc0f7f [GCFrame: 00007ffdfcac01f0]
> dumpobj 0x00007faf980081d8
Name: System.String
MethodTable: 00007fafbec30f90
EEClass: 00007fafbeb9e1b0
Size: 44(0x2c) bytes
File: /usr/share/dotnet/shared/Microsoft.NETCore.App/3.1.3/System.Private.CoreLib.dll
String: hello world
Fields:
MT Field Offset Type VT Attr Value Name
00007fafbec2a0e8 400022a 8 System.Int32 1 instance 11 _stringLength
00007fafbec26f00 400022b c System.Char 1 instance 68 _firstChar
00007fafbec30f90 400022c 108 System.String 0 static 00007faf97fff360 Empty
>
就这么简单,不过这个工具虽好,但是不能调试非托管堆,而且命令也不是太多,当然够我们平时用了。
要想实现windbg级别的调试,可以使用lldb调试器,这个非常强大,这里我也来介绍一下吧。
lldb是使用C++写的,也可以在 https://github.com/dotnet/diagnostics/blob/master/documentation/building/linux-instructions.md 寻找安装办法。
sudo yum install centos-release-SCL epel-release
sudo yum install cmake cmake3 gcc gcc-c++ gdb git libicu libunwind make python27 tar wget which zip
cd $HOME
git clone https://github.com/dotnet/diagnostics.git
$HOME/diagnostics/documentation/lldb/centos7/build-install-lldb.sh
一阵抽搐后就安装好了,从下面可以看到目前版本是3.9.1。
[root@10-25-198-96 cs2]# lldb -v
lldb version 3.9.1 ( revision )
跟windbg一样,你需要生成一个sos.dll 。。。 同样也是使用 dotnet-sos 生成。
[root@10-25-198-96 cs2]# dotnet tool install -g dotnet-sos
You can invoke the tool using the following command: dotnet-sos
Tool 'dotnet-sos' (version '3.1.122203') was successfully installed.
[root@10-25-198-96 cs2]# dotnet-sos install
Installing SOS to /root/.dotnet/sos from /root/.dotnet/tools/.store/dotnet-sos/3.1.122203/dotnet-sos/3.1.122203/tools/netcoreapp2.1/any/linux-x64
Installing over existing installation...
Creating installation directory...
Copying files...
Updating existing /root/.lldbinit file - LLDB will load SOS automatically at startup
Cleaning up...
SOS install succeeded
从上面信息看,sos 是安装在 /root/.dotnet/sos 目录下,同时也看到在lldb启动的时候会自动加载sos.dll 。。。
每个dotnet版本下都有一个createdump程序,可以用它生成dump文件,具体配置文档可以参见:
https://github.com/dotnet/diagnostics/blob/master/documentation/debugging-coredump.md
https://github.com/dotnet/runtime/blob/master/docs/design/coreclr/botr/xplat-minidump-generation.md#configurationpolicy
[root@10-25-198-96 cs2]# ps -ef | grep dotnet
root 31555 31247 0 22:28 pts/0 00:00:00 dotnet cs2.dll
root 32112 31995 0 22:29 pts/2 00:00:00 grep --color=auto dotnet
[root@10-25-198-96 cs2]# find / -name createdump
/usr/share/dotnet/shared/Microsoft.NETCore.App/3.1.3/createdump
[root@10-25-198-96 3.1.3]# ./createdump 31555 -f /lldb/test.dump
Writing minidump with heap to file /lldb/test.dump
Written 84692992 bytes (20677 pages) to core file
[root@10-25-198-96 3.1.3]# lldb --core /lldb/test.dump
(lldb) target create --core "/lldb/test.dump"
Core file '/lldb/test.dump' (x86_64) was loaded.
(lldb) clrstack -l
OS Thread Id: 0x7b43 (1)
00007FFDFCABF9E0 00007FAFBEBB037D cs2.Program.Main(System.String[]) [/cs2/Program.cs @ 13]
LOCALS:
0x00007FFDFCABF9F0=0x00007faf980081d8
00007FFDFCABFD08 00007fb037fc0f7f [GCFrame: 00007ffdfcabfd08]
00007FFDFCAC01F0 00007fb037fc0f7f [GCFrame: 00007ffdfcac01f0]
(lldb) dumpobj 0x00007faf980081d8
Name: System.String
MethodTable: 00007fafbec30f90
EEClass: 00007fafbeb9e1b0
Size: 44(0x2c) bytes
File: /usr/share/dotnet/shared/Microsoft.NETCore.App/3.1.3/System.Private.CoreLib.dll
String: hello world
Fields:
MT Field Offset Type VT Attr Value Name
00007fafbec2a0e8 400022a 8 System.Int32 1 instance 11 _stringLength
00007fafbec26f00 400022b c System.Char 1 instance 68 _firstChar
00007fafbec30f90 400022c 108 System.String 0 static 00007faf97fff360 Empty
(lldb)
可以看到,通过lldb也可以直接打入clr内部啦。。。
我觉得这篇文章肯定能给很多朋友节省不少的时间,想起朱一旦的那句话:有钱人的快乐,就是这么朴实无华且枯燥, 哈哈~
有使用数据集合操作的DevExtreme ASP.NET MVC控件都有DataSource()方法,与其他控制方法不同,DataSource()在DevExtreme JavaScript API中没有直接对应物,尽管它的用途类似于DevExtreme数据层中的 Store。您可以使用 DataSource() 方法来配置来自不同来源的数据访问:
数据绑定控件(PivotGrid 除外)也具有 DataSourceOptions() 方法,它公开了一个构建器,用于配置初始排序、过滤、分组和其他数据整形操作,构建器的方法具有本节中描述的 JavaScript API 对应项。
DevExtreme Complete Subscription官方最新版免费下载试用,历史版本下载,在线文档和帮助文件下载-慧都网
您可以将DevExtreme ASP.NET MVC 控件绑定到 CLR 对象的集合:Array、List 或 IEnumerable。
这些集合…
集合作为 ArrayStore 传递给客户端,请注意,集合数据应该是 JSON 可序列化的。
示例:将控件绑定到数组
Razor C#
@(Html.DevExtreme().Chart()
.DataSource(new[] {
new { Day="Monday", Oranges=3 },
new { Day="Tuesday", Oranges=2 },
new { Day="Wednesday", Oranges=3 },
new { Day="Thursday", Oranges=4 },
new { Day="Friday", Oranges=6 },
new { Day="Saturday", Oranges=11 },
new { Day="Sunday", Oranges=4 }
})
)
Razor VB
@(Html.DevExtreme().Chart() _
.DataSource({
New With {.Day="Monday", .Oranges=3},
New With {.Day="Tuesday", .Oranges=2},
New With {.Day="Wednesday", .Oranges=3},
New With {.Day="Thursday", .Oranges=4},
New With {.Day="Friday", .Oranges=6},
New With {.Day="Saturday", .Oranges=11},
New With {.Day="Sunday", .Oranges=4}
})
)
示例:将控件绑定到模型
本示例代码展示了如何将 MultiView 控件绑定到 Model。
View
Razor C#
@model List<DevExtreme.MVC.Demos.Models.Store>
@(Html.DevExtreme().MultiView()
.DataSource(Model)
)
Razor VB
@ModelType List(Of DevExtreme.MVC.Demos.Models.Store)
@(Html.DevExtreme().MultiView() _
.DataSource(Model)
)
Model
C#
namespace DevExtreme.MVC.Demos.Models.SampleData {
public partial class SampleData {
public static Store[] Stores=new[] {
new Store {
ID=1,
CompanyName="SuprMart",
Address="702 SW 8th Street",
City="Bentonville",
},
new Store {
ID=2,
CompanyName="El'Depot",
Address="2455 Paces Ferry Road NW",
City="Atlanta",
},
new Store {
ID=3,
CompanyName="K&S Music",
Address="1000 Nicllet Mall",
City="Minneapolis",
}
};
}
}
VB
Namespace DevExtreme.MVC.Demos.Models.SampleData
Partial Public Class SampleData
Public Shared Stores As Store()={
New Store With {
.ID=1,
.CompanyName="SuprMart",
.Address="702 SW 8th Street",
.City="Bentonville"
},
New Store With {
.ID=2,
.CompanyName="El'Depot",
.Address="2455 Paces Ferry Road NW",
.City="Atlanta"
},
New Store With {
.ID=3,
.CompanyName="K&S Music",
.Address="1000 Nicllet Mall",
.City="Minneapolis"
}
}
End Class
End Namespace
Controller
C#
using DevExtreme.MVC.Demos.Models.SampleData;
namespace DevExtreme.MVC.Demos.Controllers {
public class MultiViewController : Controller {
public ActionResult Overview() {
return View(SampleData.Stores);
}
}
}
VB
Imports DevExtreme.MVC.Demos.Models.SampleData
Namespace DevExtreme.MVC.Demos.Controllers
Public Class MultiViewController
Inherits Controller
Public Function Overview() As ActionResult
Return View(SampleData.Stores)
End Function
End Class
End Namespace
DataSource 方法重载接受 string[] 键参数,因此您的代码如下所示:
C#
.DataSource(Model.YourCollection, "KeyFieldName")
VB
.DataSource(Model.YourCollection, "KeyFieldName")
DevExtreme
DevExtreme拥有高性能的HTML5 / JavaScript小部件集合,使您可以利用现代Web开发堆栈(包括React,Angular,ASP.NET Core,jQuery,Knockout等)构建交互式的Web应用程序。从Angular和Reac,到ASP.NET Core或Vue,DevExtreme包含全面的高性能和响应式UI小部件集合,可在传统Web和下一代移动应用程序中使用。 该套件附带功能齐全的数据网格、交互式图表小部件、数据编辑器等。
起栈想必会听到这样几个关键词:后进先出,先进后出,入栈,出栈。
栈这种数据结构,数组完全可以代替其功能。
但是存在即是真理,其目的就是避免暴露不必要的操作。
如角色一样,不同的情景或者角色拥有不同的操作权限。
那我们来了解一下栈,栈是一种线性数据结构,并且只能从一端压入或者弹出=添加或者删除。
基于数组实现的栈是顺序栈,基于链表实现的链式栈。
注释说的很明白:这是一个基于数组实现的顺序栈,其入栈复杂度O(n),出栈复杂度为O(1)
// A simple stack of objects. Internally it is implemented as an array,
// so Push can be O(n). Pop is O(1).
[DebuggerTypeProxy(typeof(System.Collections.Stack.StackDebugView))]
[DebuggerDisplay("Count={Count}")]
[System.Runtime.InteropServices.ComVisible(true)]
[Serializable]
public class Stack : ICollection, ICloneable {
private Object[] _array; // Storage for stack elements
[ContractPublicPropertyName("Count")]
private int _size; // Number of items in the stack.
private int _version; // Used to keep enumerator in sync w/ collection.
[NonSerialized]
private Object _syncRoot;
private const int _defaultCapacity=10;
public Stack() {
_array=new Object[_defaultCapacity];
_size=0;
_version=0;
}
入栈:我们也看到这个是基于数组并且支持的那个太扩容的栈,默认大小围为10,当存满之后就会两倍扩容。
// Pushes an item to the top of the stack.
//
public virtual void Push(Object obj) {
//Contract.Ensures(Count==Contract.OldValue(Count) + 1);
if (_size==_array.Length) {
Object[] newArray=new Object[2*_array.Length];
Array.Copy(_array, 0, newArray, 0, _size);
_array=newArray;
}
_array[_size++]=obj;
_version++;
}
正如注释中提到的,入栈复杂度可以达到O(n),出栈可以是O(1)
so Push can be O(n). Pop is O(1).
出栈Pop,复杂度为O(1)很好理解,
上面提到的动态栈是操作受限的数组,并且不会产生新的内存申请或者数据的搬移
我们只要是获取到最后一个,然后弹出(也就是删除)即可。
入栈Push,我们接下来分析一下出栈为什么复杂度为O(n):
前面有一篇《算法复杂度》 https://www.cnblogs.com/sunchong/p/9928293.html ,提到过:最好、最坏、平均
对于下面的入栈代码:
最好情况时间复杂度:不会有扩容和数据迁移,所以直接追加即可,复杂度为O(1)
最坏情况时间复杂度:需要扩容后并且搬移原来n个数据,然后再插入新的数据,复杂度为 O(n)
那么平均时间复杂度是多少呢?这里需要结合摊还分析法,进行复杂度分析。为什么这里要使用摊还分析法呢?
先耐心地看看其定义:
分析一个操作序列中所执行的所有操作的平均时间分析方法。
与一般的平均分析方法不同的是,它不涉及概率的分析,可以保证最坏情况下每个操作的平均性能。
总结一下摊还分析:执行的所有操作的平均时间,不扯概率。
我们可以拿摊还分析来直接分析:
public virtual void Push(Object obj)
{
//Contract.Ensures(Count==Contract.OldValue(Count) + 1);
if (_size==_array.Length)
{
Object[] newArray=new Object[2*_array.Length];
Array.Copy(_array, 0, newArray, 0, _size);
_array=newArray;
}
_array[_size++]=obj;
_version++;
}
入栈的最坏情况复杂度是O(n),但是这个最坏情况时间复杂度是在n+1次插入的时候发生的,
剩下的n次是不需要扩容搬移数据,只是简单的入栈 O(1),所以均摊下来的复杂度是O(1)。
那么为什么微软的工程师在备注里写下push复杂度是O(n)?--这里指的是空间复杂度O(n)
栈这种数据结构的应用有很多场景,其中一种就是我们的线程栈或者说函数栈。
当开启一个线程时,Windows系统为每个线程分配一个1M大小的线程栈。分配这个用来干什么呢?
存储方法中的参数和变量。趁这个机会,我们了解一下CLR的内存模型。
首先对于C#代码是如何编程机器代码的呢?
c#代码 -> 编译器 -> IL -> JIT -> CPU指令
这段代码也很简单,就是在Main方法中调用了GetCode()方法,其他的都是一些临时变量。
static void Main(string[] args)
{
string parentCode="PI001";
string newCode=string.Empty;
newCode=GetCode(parentCode);
}
static string GetCode(string sourceCode)
{
string currentMaxCode="001001";
string newCode=$"{sourceCode}{currentMaxCode}";
return newCode;
}
首先线程会分配1M内存用于存储临时变量和参数;
进入Main方法时,会将参数和返回地址依次压栈
static void Main(string[] args)
string parentCode="PI001";
string newCode=string.Empty;
接下来开始进入到 GetCode() 方法,此时与之前一样压入参数和返回地址
string currentMaxCode="001001";
string newCode=$"{sourceCode}{currentMaxCode}";
既然说到了栈,那我们不得不再说一下托管堆,再来一段代码图解:
这是父类和子类的具体代码,可以略过此处。
我们隐藏这些方法的具体实现,只预览他们的之间的关系:
接下来,线程即将进入到下面的这个方法:
void GetProduct()
{
BaseProduct p;
string sourceCode;
p=new Product();
sourceCode=p.GetCode();
sourceCode=Product.GetNewCode(sourceCode);
}
JIT在编译到此方法前会注意到这个方法所有的引用类型,
并向CLR发出通知,加载程序集,在托管对中创建相关的类型对象数据结构。
这也就是我们所能理解的静态字段为什么不是在实例化的时候创建,
而是在这个类型创建的时候就一直存在。
这其实就是两个概念,静态资源是属于类结构的,而实例资源是属于实例本身。
下面的图忽略String类型,因为String类型是常用类型可能在之前就已经创建好了,
类型对象包括:对象指针、同步块索引、静态资源、方法表,像下面这样:
BaseProduct p;
string sourceCode;
方法变量入栈,并且引用类型初始化为null
p=new Product();
实例化Product ,托管堆创建Product对象,并将这个对象的指针指向Product类型。
将线程栈中的变量p指针,指向新创建的Product对象。
sourceCode=p.GetCode();
JIT找到这个变量p的Product对象,再找到对应的Product类型,表中找到此方法GetCode();
当然这个方法实际是父类的方法,所以JIT会一直向上找,直到找到为止。
图中是个虚拟路线,计算结果赋值给 string sourceCode
sourceCode=Product.GetNewCode(sourceCode);
和上一步类似,只不过这次是调用的静态方法。发出类型Product,静态方法是GetNewCode()
以上内容就是 JIT、CLR、线程栈、托管堆的一些运行时关系。
*请认真填写需求信息,我们会在24小时内与您取得联系。