Windbg Extension NetExt 使用指南 【2】 ---- NetExt 的基本命令介绍
阅读原文时间:2020年09月22日阅读:1

摘要 : 本章节介绍NetExt常用的命令. 并且对SOS进行一些对比.

NetExt的帮助

要想玩好NetExt, 入门就得看帮助. 看NetExt的帮助可以调用!whelp 命令. 这样hi列举出NetExt所支持的所有命令.

0:000> !netext.whelp
netext version 2.0.0.5000 Feb 9 2015
License and usage can be seen here: !whelp license
Check Latest version: !wupdate
For help, type !whelp (or in WinDBG run: '.browse !whelp')
Questions and Feedback: http://netext.codeplex.com/discussions
Copyright (c) 2014-2015 Rodney Viana (http://blogs.msdn.com/b/rodneyviana)
Type: !windex -tree or ~*e!wstack to get started

Show Object Detail Commands

!wdo - Display ad-hoc objects or arrays from GAC or Stack
!wselect - Display ad-hoc fields (and level fields) for an object or for all item in an array
!wfrom - Perform SQL-like analysis of Heap objects enabling comparison, expression evaluation and indexed filtering.
*(new)* !wpe - Dump Exception Object

Enumerate objects

!windex - index and display objects based in different filters like object with of type HttpContext
!wstack - dump unique stack objects
!wheap - list objects without indexing and show thottled heap sampling
!wgchandle - Dump GC root handles
*(new)* !wdae - Dump All Exceptions

Process commands

!wclrstack - Dump current stack trace (only managed thread)
*(new)* !wthreads
*(new)* !wver - Show CLR version and extension version
*(new)* !wupdate - Check for update

Special

!wdict - Display dictionary objects
!whash - Display HashTable objects
!whttp - List HttpContext Objects
!wconfig - Show all .config file lines in memory
!wservice - List WCF service Objects
!weval - Evaluate expression list
!wkeyvalue - Display pair key/value for NameObjectCollection type objects
!wcookie - Display HTTP cookies
!wruntime - Display HTTP Runtime Info including Active Requests
!wtoken - Display WIF tokens and cookies

Misc

expression syntax
functions list *new functions*
license see all licenses applied to this product

如果想要知道具体更为具体的内容, 需要在!whelp 后面加上这个命令. 例如, NetExt中用得非常多的一个命令!wdo. 这个命令相当于!sos.do. 如果想要知道它更为详细的说明, 可以执行命令 !whelp wdo

0:000> !netext.whelp wdo
Display ad-hoc objects or arrays from GAC or Stack

Usage: !wdo [-forcearray] [-shownull] [-noheader] [-noindex] [-tokens] [-mt ]
[-start ] [-end ]
Where:
-mt is the method table address of the object (for value objects). Optional
-start lt;expr> is the starting index of an array. Optional. Default is starting array at item 0
-end is the end index of an array. Optional. Default is ending array at max items
-forcearray if not used Byte[] and Char[] arrays will show as string instead of array items. Optional
-shownull if not used will show only non-empty array items. Optional
-noheader if present will hide headers and show only object address, fields and values. Optional
-noindex if present will not show array index (useful for .foreach). Optional
-tokens if present will show class token and field token
is the object or array address (you can use an expression). Required

Improvements over !DumpObj:

- Object, struct and nested class fields show address value with link to detail the field. Click link to follow  
- Numeric type fields show both hex and decimal values  
- For Enum type fields it shows the type name (or type names for \[Flag\]Enum) along with the numeric value  
- For TimeSpan and DateTime fields it shows the string equivalent  
- For strings it show the content after the address  
- It shows the inheritance chain in the header (not interface implementations though)  
- Byte\[\] and Char\[\] objects shows as string if you do not use -forcearraty  
- By default, array objects only show non-null items  
- Static fields show the value for the first valid application domain where it is defined  
- For arrays it lists all components

Examples:

Listing an object ad-hoc

!wdo -tokens 00000001556e24e8  
Address: 00000001556e24e8  
EEClass: 000007feef8c2d50  
Method Table: 000007feefc40268  
Class Name: System.Runtime.Remoting.Lifetime.Lease  
Size : 96  
Instance Fields: 12  
Static Fields: 1  
Total Fields: 13  
Heap: 0  
Generation: 0  
Module: 000007feef7d1000  
Assembly: 0000000001373e00  
Domain: 000007fef2767880  
Dynamic: false  
Assembly name: C:\\Windows\\assembly\\GAC\_64\\mscorlib\\2.0.0.0\_\_b77a5c561934e089\\mscorlib.dll  
Inherits: System.Runtime.Remoting.Lifetime.Lease System.Object System.MarshalByRefObject (000007FEEFC40268 000007FEEFC07370 000007FEEFC0AD70)  
400018a 000007feefc07370         2000052                            System.Object +0000         \_\_identity 0000000000000000  
400201e 000007feefc0ecf0         2000702                             System.Int32 +0020                 id 0 (0n0)  
400201f 000007feefc47fb8         2000702                          System.DateTime +0030          leaseTime -mt 000007FEEFC47FB8 00000001556E2520 10/26/2011 9:21:08 PM  
4002020 000007feefc47eb8         2000702                          System.TimeSpan +0038   initialLeaseTime -mt 000007FEEFC47EB8 00000001556E2528 00:05:00  
4002021 000007feefc47eb8         2000702                          System.TimeSpan +0040    renewOnCallTime -mt 000007FEEFC47EB8 00000001556E2530 00:02:00  
4002022 000007feefc47eb8         2000702                          System.TimeSpan +0048 sponsorshipTimeout -mt 000007FEEFC47EB8 00000001556E2538 00:02:00  
4002023 000007feefc06c50         2000702                           System.Boolean +002c         isInfinite 0 (False)  
4002024 000007feefc0f3d8         2000702             System.Collections.Hashtable +0008       sponsorTable 00000001556E2548  
4002025 000007feefc0ecf0         2000702                             System.Int32 +0024  sponsorCallThread 0 (0n0)  
4002026 000007feefc41568         2000702 System.Runtime.Remoting.Lifetime.LeaseMa +0010       leaseManager 00000001556E1FD0  
4002027 000007feefc0ad70         2000702                System.MarshalByRefObject +0018      managedObject 0000000155663A40  
4002028 000007fef03aefc0         2000702 System.Runtime.Remoting.Lifetime.LeaseSt +0028              state 2 (0n2) Active  
4002029 000007feefc0ecf0 Static  2000702                             System.Int32 +0c78             nextId NoInit

Listing only items 5 to 10 (oxa) from an array

!wdo -start 5 -end a 000000016d29cb50  
Address: 000000016d29cb50  
EEClass: 00000642780e0cf8  
Method Table: 000006427843e2a8  
Class Name: System.Collections.Hashtable+bucket\[\]  
Size : 576  
Rank: 1  
Components: 23  
\[5\]: 000000016d29cbd8  
\[6\]: 000000016d29cbf0  
\[7\]: 000000016d29cc08  
\[8\]: 000000016d29cc20  
\[9\]: 000000016d29cc38  
\[10\]: 000000016d29cc50  
\* Note: for HashTables you can use !whash

Compare to:

!wselect - also shows add-hoc objects and arrays, however wselect enables the selection of fields (and field levels) and does not show extensive header  
!wfrom - enable complex query mechanism with categories, conditions and functions but it is not as simple to use as wselect and wdo

How do I get object addresses?

- Type !windex -enumtypes or !windex -tree to enumerate heap objects  
- Type ~\*e!wstack to  list all objects in the stack for all threads  
- Type !wheap to show a quick heap sampling without indexing. It will yield a throttled but quick output

玩转DUMP OBJECT

SOS中可以用!do里面把Object的信息DUMP出来. NetExt中, 则是使用!wdo. 从表面看, 他们显示的结果并没有多大区别.

0:014> !do 00000001957775e0
Name: System.RuntimeFieldInfoStub
MethodTable: 000007fef0b9c950
EEClass: 000007fef07d1dc0
Size: 72(0x48) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
000007fef0b95a48 40005c6 8 System.Object 0 instance 0000000000000000 m_keepalive
000007fef0b95a48 40005c7 10 System.Object 0 instance 0000000000000000 m_c
000007fef0b95a48 40005c8 18 System.Object 0 instance 0000000000000000 m_d
000007fef0b9c7d8 40005c9 30 System.Int32 1 instance 0 m_b
000007fef0b95a48 40005ca 20 System.Object 0 instance 0000000000000000 m_e
000007fef0b95a48 40005cb 28 System.Object 0 instance 0000000000000000 m_f
000007fef0ba6d98 40005cc 38 …eldHandleInternal 1 instance 0000000195777618 m_fieldHandle

0:014> !netext.wdo 00000001957775e0
Address: 00000001957775e0
Method Table/Token: 000007fef0b9c950/200014a04
Class Name: System.RuntimeFieldInfoStub
Size : 72
EEClass: 000007fef07d1dc0
Instance Fields: 7
Static Fields: 0
Total Fields: 14
Heap/Generation: 1/0
Module: 00000000f06d0000
Assembly: 0000000000da0870
Domain: 00000000f3f75580
Assembly Name: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Inherits: System.Object (000007FEF0B95A48)
000007fef0b95a48 System.Object +0000 m_keepalive 0000000000000000
000007fef0b95a48 System.Object +0008 m_c 0000000000000000
000007fef0b95a48 System.Object +0010 m_d 0000000000000000
000007fef0b95a48 System.Object +0018 m_e 0000000000000000
000007fef0b95a48 System.Object +0020 m_f 0000000000000000
000007fef0b9c7d8 System.Int32 +0028 m_b 0 (0n0)
000007fef0ba6d98 System.RuntimeFieldHandleInternal +0030 m_fieldHandle -mt 000007FEF0BA6D98 0000000195777618

!wdo在细节上则做的更加体贴. 例如dump的对象中包含了一个string类型, !wdo会将string的地址显示出来. 对于枚举类型, 则会将枚举对应的含义显示出来.  !do命令显示出来的结果还必须手工的再对string的地址进行操作. 对枚举的类型的翻译也相当的贴心, 减少了机械枯燥的工作也节省了时间.

0:014> !do 000000019588b000
Name: System.Uri
MethodTable: 000007feeec9b358
EEClass: 000007feee995d30
Size: 72(0x48) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll
Fields:
MT Field Offset Type VT Attr Value Name
000007fef0b968f0 400161c 8 System.String 0 instance 000000019571ede0 m_String
000007fef0b968f0 400161d 10 System.String 0 instance 0000000000000000 m_originalUnicodeString
000007feeec9f698 400161e 18 System.UriParser 0 instance 000000019568c020 m_Syntax
000007fef0b968f0 400161f 20 System.String 0 instance 0000000000000000 m_DnsSafeHost
000007feeecf2778 4001620 30 System.UInt64 1 instance 37624152064 m_Flags
(…)

0:014> !wdo 000000019588b000
Address: 000000019588b000
Method Table/Token: 000007feeec9b358/200037704
Class Name: System.Uri
Size : 72
EEClass: 000007feee995d30
Instance Fields: 7
Static Fields: 21
Total Fields: 14
Heap/Generation: 1/0
Module: 00000000ee980000
Assembly: 0000000003ec58a0
Domain: 00000000f3f75580
Assembly Name: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll
Inherits: System.Object (000007FEF0B95A48)
000007fef0b968f0 System.String +0000 **m_String 000000019571ede0 http://rviana-serv.northamerica.corp.microsoft.com:2000/Service.svc** 000007fef0b968f0 System.String +0008 m_originalUnicodeString 0000000000000000 (null)
000007feeec9f698 System.UriParser +0010 m_Syntax 000000019568c020
000007fef0b968f0 System.String +0018 m_DnsSafeHost 0000000000000000 (null)
000007feeec9fbb8 System.Uri+UriInfo +0020 m_Info 000000015572ea20
000007feeecf2778 System.Uri+Flags +0028 *m_Flags 8c2930000 (0n37624152064) IPv6HostType|IPv4HostType|DnsHostType|AuthorityFound|NotDefaultPort|CanonicalDnsHost|MinimalUriInfoSet|AllUriInfoSet|RestUnicodeNormalized*
(…)

另外一个优势在于对数组的处理. SOS的!do并不能显示数组里面的内容, 需要使用!dumparray命令.

0:014> !do 00000001957ab4a8
Name: System.Byte[]
MethodTable: 000007fef0ba0b40
EEClass: 000007fef0722310
Size: 4120(0x1018) bytes
Array: Rank 1, Number of elements 4096, Type Byte (Print Array)
Element Type:System.Byte
Content: <link rel="alternate" type="text/xml" href="http://rviana-serv.northamerica.corp.microsoft.com:2000/Service.svc?disc
Fields:
None

0:014> !dumparray -details 00000001957ab4a8
Name: System.Byte[]
MethodTable: 000007fef0ba0b40
EEClass: 000007fef0722310
Size: 4120(0x1018) bytes
Array: Rank 1, Number of elements 4096, Type Byte
Element Methodtable: 000007fef0b9c158
[0] 00000001957ab4b8
Name: System.Byte
MethodTable: 000007fef0b9c158
EEClass: 000007fef0720398
Size: 24(0x18) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
000007fef0b9c158 4000276 0 System.Byte 1 instance 60 m_value
[1] 00000001957ab4b9
Name: System.Byte
MethodTable: 000007fef0b9c158
EEClass: 000007fef0720398
Size: 24(0x18) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
000007fef0b9c158 4000276 0 System.Byte 1 instance 72 m_value

(…)

使用!wdo时候, 他会尝试将这些数组翻译成一些有意义的内容. 例如上面的byte[]数组, 其实可以拼成一组字符串.

0:014> !wdo 00000001957ab4a8
Address: 00000001957ab4a8
Method Table/Token: 000007fef0ba0b40/200000004
Class Name: System.Byte[]
Size : 4120
EEClass: 000007fef0722310
Rank: 1
Components: 4096
Data Start: 00000001957ab4b8
Service Service

Service Service


You have created a service.

To test this service, you will need to create a client and use it to call the service. You can do this using the svcutil.exe tool from the command line with the following syntax:


svcutil.exe http://rviana-serv.northamerica.corp.microsoft.com:2000/Service.svc?wsdl

This will generate a configuration file and a code file that contains the client class. Add the two files to your client application and use the generated client class to call the Service. For example:

C#

class Test  
{  
static void Main()
{
ServiceClient client = new ServiceClient();

// Use the 'client' variable to call operations on the service.

// Always close the client. client.Close();
}
}


Visual Basic

Class Test  
    Shared Sub Main()  
Dim client As ServiceClient = New ServiceClient()
' Use the 'client' variable to call operations on the service.

' Always close the client. client.Close()
End Sub End Class

当然也可以选择以数组的形式进行检查, 同时也可以指定一定的范围. 例如可以用!wdo并且带上参数 -forcearray -start 以及 -end 进行约束.

0:014> !wdo -forcearray -start 0n10 -end 0n15 00000001957ab4a8
Address: 00000001957ab4a8
Method Table/Token: 000007fef0ba0b40/200000004
Class Name: System.Byte[]
Size : 4120
EEClass: 000007fef0722310
Rank: 1
Components: 4096
Data Start: 00000001957ab4b8
[10]: 0x44 (0n68)
[11]: 0x3e (0n62)
[12]: 0x3c (0n60)
[13]: 0x6c (0n108)
[14]: 0x69 (0n105)
[15]: 0x6e (0n110)

还能用-noheader的参数减少输出的内容, 可以关注具体字段里面的数据. 通常是配合一些脚本一起使用.

每一列的意义如下 :

Column

意义

0

当前field的类型的Method Definition Table的地址

1

如果显示Static, 则说明这个field是个Static类型, 否则是个instance

2

类型的名称

3

field的偏移量.

4

field name

5

这个field的具体内容, 如果是个值类型, 则是它的值, 如果是引用类型则显示它的地址.

6

如果是一个常用类型, 则显示他的具体内容, 常用类型包括datetime, string, guid等等

!wselect初体验

另外一个非常强大的功能是!wselect命令. 这个命令与!wdo很类似, 也是用dump object.

0:014> !wselect * from 000000019588b000
[System.Uri]
Known Type Value: http://rviana-serv.northamerica.corp.microsoft.com:2000/Service.svc
(string)System.String m_String = http://rviana-serv.northamerica.corp.microsoft.com:2000/Service.svc
(string)System.String m_originalUnicodeString = NULL
System.UriParser m_Syntax = 000000019568C020
(string)System.String m_DnsSafeHost = NULL
System.Uri+UriInfo m_Info = 000000015572EA20
(…)

如果你认为他只能做到这些就图样图森破了. 它还可以只显示个别特定的field, 当然你必须指定field的名称.

0:014> !wselect m_String, m_Flags from 000000019588b000
[System.Uri]
Known Type Value: http://rviana-serv.northamerica.corp.microsoft.com:2000/Service.svc (string)System.String m_String = http://rviana-serv.northamerica.corp.microsoft.com:2000/Service.svc
(uint64)System.Uri+Flags m_Flags = 8c2930000 (0n37624152064) IPv6HostType|IPv4HostType|DnsHostType|AuthorityFound|NotDefaultPort|CanonicalDnsHost|MinimalUriInfoSet|AllUriInfoSet|RestUnicodeNormalized

不仅如此, 它还可以将field所对应的Object下面的field也dump出来. 这个功能非常的实用. 当我需要重复性的检查一些固定模式的对象值的时候, 可以用他做成一个脚本, 然后一劳永逸的一直执行下去.

例如, 如果我现在知道HttpContext的地址. 然后我需要知道它对应的请求的UTC Time Stamp, http Method, URL, Response status code等等内容时, 应该怎么办? 这些数据并不全都直接显示在HttpContext上面. UTC Time Stamp在HttpContext上面可以找到. HttpMethod则在HttpContext下面的HttpRequest下面this._request._httpMethod. URI则离得更远, _request._url.m_String. Response Status Code又在另外一个对象上this._response._statusCode. 下面举例我如何通过SOS去DUMP一个Http的请求地址.

0:014> !do 00000001956f77a8
Name: System.Web.HttpContext
MethodTable: 000007fed5396100
EEClass: 000007fed505b938
Size: 344(0x158) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_64\System.Web\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Web.dll
Fields:
MT Field Offset Type VT Attr Value Name
000007fed5395598 4000cab 8 …IHttpAsyncHandler 0 instance 0000000000000000 _asyncAppHandler
000007fed53950c0 4000cac 10 …b.HttpApplication 0 instance 0000000000000000 _appInstance
000007fed5395610 4000cad 18 ….Web.IHttpHandler 0 instance 0000000000000000 _handler
000007fed5396558 4000cae 20 …m.Web.HttpRequest 0 instance 00000001956f7900 _request

(…)

0:014> !do 00000001956f7900
Name: System.Web.HttpRequest
MethodTable: 000007fed5396558
EEClass: 000007fed505b9a0
Size: 360(0x168) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_64\System.Web\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Web.dll
Fields:
MT Field Offset Type VT Attr Value Name
000007fed5399f48 4000d40 8 …HttpWorkerRequest 0 instance 00000001956f7570 _wr
000007fed5396100 4000d41 10 …m.Web.HttpContext 0 instance 00000001956f77a8 _context
000007fef0b968f0 4000d42 18 System.String 0 instance 00000001956fa860 _httpMethod
000007fed53cb4c0 4000d43 148 System.Int32 1 instance 2 _httpVerb
000007fef0b968f0 4000d44 20 System.String 0 instance 0000000000000000 _requestType
000007fed538ac98 4000d45 28 …m.Web.VirtualPath 0 instance 00000001956ff140 _path
000007fef0b968f0 4000d46 30 System.String 0 instance 0000000000000000 _rewrittenUrl
000007fef0b9d608 4000d47 150 System.Boolean 1 instance 0 _computePathInfo
000007fed538ac98 4000d48 38 …m.Web.VirtualPath 0 instance 00000001956fe068 _filePath
000007fed538ac98 4000d49 40 …m.Web.VirtualPath 0 instance 0000000000000000 _currentExecutionFilePath
000007fed538ac98 4000d4a 48 …m.Web.VirtualPath 0 instance 0000000000000000 _pathInfo
000007fef0b968f0 4000d4b 50 System.String 0 instance 0000000155660488 _queryStringText
000007fef0b9d608 4000d4c 151 System.Boolean 1 instance 0 _queryStringOverriden
000007fef0ba0b40 4000d4d 58 System.Byte[] 0 instance 0000000000000000 _queryStringBytes
000007fef0b968f0 4000d4e 60 System.String 0 instance 00000001956f76c0 _pathTranslated
000007fef0b968f0 4000d4f 68 System.String 0 instance 0000000155660488 _contentType
000007fef0b9c7d8 4000d50 14c System.Int32 1 instance -1 _contentLength
000007fef0b968f0 4000d51 70 System.String 0 instance 0000000000000000 _clientTarget
000007fef0b9adf8 4000d52 78 System.Object[] 0 instance 0000000000000000 _acceptTypes
000007fef0b9adf8 4000d53 80 System.Object[] 0 instance 0000000000000000 _userLanguages
000007fed53a3f68 4000d54 88 …owserCapabilities 0 instance 0000000000000000 _browsercaps
000007feeec9b358 4000d55 90 System.Uri 0 instance 00000001957112a0 _url

(…)

0:014> !do 00000001957112a0
Name: System.Uri
MethodTable: 000007feeec9b358
EEClass: 000007feee995d30
Size: 72(0x48) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll
Fields:
MT Field Offset Type VT Attr Value Name
000007fef0b968f0 400161c 8 System.String 0 instance 0000000195711240 m_String

(…)

0:014> !do 0000000195711240
Name: System.String
MethodTable: 000007fef0b968f0
EEClass: 000007fef071ed58
Size: 92(0x5c) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String: http://localhost:2000/Service.svc
Fields:
MT Field Offset Type VT Attr Value Name
000007fef0b9c7d8 4000103 8 System.Int32 1 instance 33 m_stringLength

从这里看到, 如果要从HttpContext上面dump Http 请求的地址, 需要执行4次!do指令. 同时还要去找到其他的对象, 那么还需要更多的步骤. 如果需要检查多个HttpContext上面相同的这些字段内容, 将是一场噩梦.

如果这个事情交给NetExt来做就相当的轻松, 只要几个命令, 一切搞定.

0:014> !wselect _utcTimestamp, _request._httpMethod, _request._url.m_String, _response._statusCode from 00000001956f77a8
[System.Web.HttpContext]
System.DateTime _utcTimestamp = -mt 000007FEF0BB96C8 00000001956F78D8 10/26/2011 11:29:15 PM
(string)System.String _request._httpMethod = GET
(string)System.String _request._url.m_String = http://localhost:2000/Service.svc
(int32)System.Int32 _response._statusCode = c8 (0n200)

从这个角度上来说, 这绝对是提高debugging效率的利器.

总结

NetExt针对debugging的工作做了非常多的优化工作.

  • 相对!sos.do, !wdo优化了显示的内容, 很多常用的类型都将会直接显示出具体的内容. 并且针对一些常用的枚举进行了转义, 可以让我们直接了解到枚举的值所代表的意义.
  • !wdo针对数组的显示进行了相当多的优化
  • !wselect同样是dump object的利器. 如果我们岙DUMP的object藏在很深的路径下, 它能够帮助我们介绍很多时间和工作量.

Sonic Guo

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章