DataTable在内存中是如何存储一张表的结构的呢?如何使用Windbg直接在内存中找到一个DataTable里面的第M列第N行的值呢?这的确是一个比较有趣的问题^_^
下面的内容,主要是讲如何用windbg+sos来看看一个DataTable在内存中是如何存储的,以及寻找内存中一个DataTable特定的行列的一个单元的值是多少。同时,还有一点Windbg Script的使用。
找一个小白鼠先:
class Program
{
static void Main(string[] args)
{
System.Data.DataTable dt = new System.Data.DataTable();
DataColumn dtC1 = new DataColumn();
dtC1.ColumnName = "Column1";
DataColumn dtC2 = new DataColumn();
dtC2.ColumnName = "Column2";
dt.Columns.Add(dtC1);
dt.Columns.Add(dtC2);
DataRow dr1 = dt.NewRow();
dr1["Column1"] = "aaaa";
dr1["Column2"] = "bbbb";
dt.Rows.Add(dr1);
DataRow dr2 = dt.NewRow();
dr2["Column1"] = "cccc";
dr2["Column2"] = "dddd";
dt.Rows.Add(dr2);
pause();
}
public static void pause()
{
System.Console.ReadLine();
}
}
下面就看看DataTable在内存里面是如何存储的,以及在内存里面找到aaaa,bbbb,cccc,dddd这几个字符串。
首先!threads看看哪几个托管线程,then,切换到主线程上面:~0s
0:000> !dumpstackobjects
OS Thread Id: 0x66c (0)
ESP/REG Object Name
0012f3b0 013b9830 Microsoft.Win32.SafeHandles.SafeFileHandle
0012f3c0 013b9830 Microsoft.Win32.SafeHandles.SafeFileHandle
0012f3f4 013ba01c System.Byte[]
0012f3f8 013b9844 System.IO.__ConsoleStream
0012f418 013b97c8 System.Data.DataRow
0012f41c 013b9fc4 System.IO.StreamReader
0012f420 013b9fc4 System.IO.StreamReader
0012f424 013b7dac System.Data.DataRowCollection
0012f434 013b9fc4 System.IO.StreamReader
0012f438 013ba338 System.IO.TextReader+SyncTextReader
0012f43c 013b7dac System.Data.DataRowCollection
0012f444 013b97c8 System.Data.DataRow
0012f448 013b9664 System.Data.DataRow
0012f44c 013ba338 System.IO.TextReader+SyncTextReader
0012f450 013b7dac System.Data.DataRowCollection
0012f458 013b6120 System.Data.DataTable
0012f468 013b5b7c System.Object[] (System.String[])
0012f46c 013b82fc System.Data.DataColumn
0012f470 013b8edc System.Data.DataColumn
0012f534 013b5b7c System.Object[] (System.String[])
0012f6e0 013b5b7c System.Object[] (System.String[])
0012f708 013b5b7c System.Object[] (System.String[])
有几个DataColumn和几个DataRow,还有两个DataRowCollection。为了证实猜想,继续查看下去,dump DataTable:
0:000> !do 013b6120
Name: System.Data.DataTable
MethodTable: 653c46d8
EEClass: 653c4288
Size: 296(0x128) bytes
Fields:
MT Type Value Name
7a745c0c | ...ponentModel.ISite | 000000000 | site |
7a742e54 | ....EventHandlerList | 000000000 | events |
790f9c18 | System.Object | 000000000 | EventDisposed |
653c2e2c | System.Data.DataSet | 000000000 | dataSet |
653c6ce8 | System.Data.DataView | 000000000 | defaultView |
653c72cc | ...DataRowCollection | 0013b7dac | rowCollection |
653c33d4 | ...aColumnCollection | 0013b7c5c | columnCollection |
653c711c | ...straintCollection | 0013b7d74 | constraintCollection |
653d3d0c | ...elationCollection | 000000000 | parentRelationsCollection |
653d3d0c | ...elationCollection | 000000000 | childRelationsCollection |
653c7080 | ...ata.RecordManager | 0013b7500 | recordManager |
790fa3e0 | System.String | 0013b6290 | tableName |
790fa3e0 | System.String | 000000000 | tableNamespace |
790fa3e0 | System.String | 0013b6290 | tablePrefix |
653e63bc | ...ta.DataExpression | 000000000 | displayExpression |
790ff4c4 | ...ation.CultureInfo | 0013b78dc | _culture |
7910feec | ...ation.CompareInfo | 000000000 | _compareInfo |
790ffdcc | ...m.IFormatProvider | 000000000 | _formatProvider |
79112d98 | ...em.StringComparer | 000000000 | _hashCodeProvider |
790fa3e0 | System.String | 000000000 | encodedTableName |
653c3e94 | ...m.Data.DataColumn | 000000000 | xmlText |
653c3e94 | ...m.Data.DataColumn | 000000000 | _colUnique |
79105ba4 | System.Decimal | 1013b6224 | minOccurs |
79105ba4 | System.Decimal | 1013b6234 | maxOccurs |
790f9c18 | System.Object | 000000000 | typeName |
653c9b84 | ....UniqueConstraint | 000000000 | primaryKey |
653d63ac | ...Data.IndexField | 0013b6274 | _primaryIndex |
653c75f8 | System.Data.Index | 000000000 | loadIndexwithOriginalAdded |
653c75f8 | System.Data.Index | 000000000 | loadIndexwithCurrentDeleted |
79124228 | System.Object[] | 000000000 | EmptyDataRowArray |
7a74db40 | ...criptorCollection | 000000000 | propertyDescriptorCollectionCache |
79124228 | System.Object[] | 0013b6280 | _nestedParentRelations |
653dfc68 | ...hangeEventHandler | 000000000 | onRowChangedDelegate |
653dfc68 | ...hangeEventHandler | 000000000 | onRowChangingDelegate |
653dfc68 | ...hangeEventHandler | 000000000 | onRowDeletingDelegate |
653dfc68 | ...hangeEventHandler | 000000000 | onRowDeletedDelegate |
653dec84 | ...hangeEventHandler | 000000000 | onColumnChangedDelegate |
653dec84 | ...hangeEventHandler | 000000000 | onColumnChangingDelegate |
653e039c | ...ClearEventHandler | 000000000 | onTableClearingDelegate |
653e039c | ...ClearEventHandler | 000000000 | onTableClearedDelegate |
653e042c | ...ewRowEventHandler | 000000000 | onTableNewRowDelegate |
7a7638a4 | ...angedEventHandler | 000000000 | onPropertyChangingDelegate |
7910d61c | System.EventHandler | 000000000 | onInitialized |
653c7730 | ...ta.DataRowBuilder | 0013b82ec | rowBuilder |
790fea70 | ...ections.Hashtable | 000000000 | rowDiffId |
79103b6c | ....ReaderWriterLock | 0013b6348 | indexesLock |
791240f0 | System.Int32[] | 0013b6248 | zeroIntegers |
79124228 | System.Object[] | 0013b6254 | zeroColumns |
79124228 | System.Object[] | 0013b6264 | zeroRows |
653d63ac | ...Data.IndexField | 0013b6274 | zeroIndexField |
79124228 | System.Object[] | 0013b6280 | EmptyArrayDataRelation |
由于DataTable里面的东西太多,就去掉了几列和一些没有太大用处的行。在elementColumnCount属性里面,可以看到DataTable里面有刚才定义的两列。
接着,可以在columnCollection这个field里面找到刚才小白鼠里面的DataTable的Column的集合:
0:000> !do 013b7c5c
Name: System.Data.DataColumnCollection
MethodTable: 653c33d4
EEClass: 653c3364
Size: 56(0x38) bytes
(C:"WINDOWS"assembly"GAC_32"System.Data"2.0.0.0__b77a5c561934e089"System.Data.dll)
Fields:
MT Type Value Name
7a753b14 ...onChangeEventArgs 00000000 RefreshEventArgs
653c46d8 ...em.Data.DataTable 013b6120 table
791036b0 ...ections.ArrayList 013b7c94 _list
790fed1c System.Int32 1 defaultNameIndex
79124228 System.Object[] 00000000 delayedAddRangeColumns
790fea70 ...ections.Hashtable 013b7cac columnFromName
7a7504bc ...hangeEventHandler 00000000 onCollectionChangedDelegate
7a7504bc ...hangeEventHandler 00000000 onCollectionChangingDelegate
7a7504bc ...hangeEventHandler 00000000 onColumnPropertyChangedDelegate
79104f64 System.Boolean 0 fInClear
79124228 System.Object[] 013b6254 columnsImplementingIChangeTracking
790fed1c System.Int32 0 nColumnsImplementingIChangeTracking
790fed1c System.Int32 0
nColumnsImplementingIRevertibleChangeTracking
在这个结构中,可以看到DataColumn是放过在_list这个ArrayList里面的。继续查看ArrayList里面都有些什么:
0:000> !dumpobj 013b7c94
Name: System.Collections.ArrayList
MethodTable: 791036b0
EEClass: 79103604
Size: 24(0x18) bytes
(C:"WINDOWS"assembly"GAC_32"mscorlib"2.0.0.0__b77a5c561934e089"mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
79124228 40008c0 4 System.Object[] 0 instance 013b8f8c _items
790fed1c 40008c1 c System.Int32 0 instance 2 _size
790fed1c 40008c2 10 System.Int32 0 instance 2 _version
790f9c18 40008c3 8 System.Object 0 instance 00000000 _syncRoot
79124228 40008c4 1b0 System.Object[] 0 shared static emptyArray
>> Domain:Value 00154598:013b1c2c <<
恩,快要到达目的地了,接着由于里面只有两个Column,我就在Windbg里面全部给输出出来了:
0:000> !dumparray -details 013b8f8c
Name: System.Object[]
MethodTable: 79124228
EEClass: 7912479c
Size: 32(0x20) bytes
Array: Rank 1, Number of elements 4, Type CLASS
Element Methodtable: 790f9c18
[0] 013b82fc
Name: System.Data.DataColumn
MethodTable: 653c3e94
EEClass: 653c3e1c
Size: 148(0x94) bytes
对于每个DataColumn的细节,输出很长,这里就截取一个_items里面的DataColumn对象的细节:
MT Type Value Name
7a745c0c ...ponentModel.ISiteinstance 00000000 site
7a742e54 ....EventHandlerListinstance 00000000 events
790f9c18 System.Object static 00000000 EventDisposed
79104f64 System.Booleaninstance 1 allowNull
79104f64 System.Booleaninstance 0 autoIncrement
790fcb80 System.Int64instance 1 autoIncrementStep
790fcb80 System.Int64instance 0 autoIncrementSeed
790fa3e0 System.Stringinstance 00000000 caption
790fa3e0 System.Stringinstance 013b6070 _columnName
79101058 System.Typeinstance 013b8390 dataType
790f9c18 System.Objectinstance 013b846c defaultValue
653d4c94 System.Int32instance 3 _dateTimeMode
653e63bc ...ta.DataExpressioninstance 00000000 expression
790fed1c System.Int32instance -1 maxLength
790fed1c System.Int32instance 0 _ordinal
79104f64 System.Booleaninstance 0 readOnly
653c75f8 System.Data.Indexinstance 00000000 sortIndex
653c46d8 ...em.Data.DataTableinstance 013b6120 table
79104f64 System.Booleaninstance 0 unique
653d3bfc System.Int32instance 1 columnMapping
790fed1c System.Int32instance 0 _hashCode
790fed1c System.Int32instance 0 errors
79104f64 System.Booleaninstance 0 isSqlType
79104f64 System.Booleaninstance 0 implementsINullable
79104f64 System.Booleaninstance 1 defaultValueIsNull
00000000 instance 00000000 dependentColumns
653d4218 ...ropertyCollectioninstance 00000000 extendedProperties
7a7638a4 ...angedEventHandlerinstance 00000000 onPropertyChangingDelegate
653c68bc ...ommon.DataStorageinstance 013b8fdc _storage
790fcb80 System.Int64instance 0 autoIncrementCurrent
790fa3e0 System.Stringinstance 00000000 _columnUri
790fa3e0 System.Stringinstance 013b6290 _columnPrefix
790fa3e0 System.Stringinstance 00000000 encodedColumnName
790fa3e0 System.Stringinstance 013b6290 description
790fa3e0 System.Stringinstance 013b6290 dttype
653c9abc ...m.Data.SimpleTypeinstance 00000000 simpleType
790fed1c System.Int32instance 1 _objectID
790fed1c System.Int32 static 2 _objectTypeCount
啊哈,看到曙光了,Column的内容,看样子就是存储在_storage里面的了,打开看看:
0:000> !dumpobj 013b8fdc
Name: System.Data.Common.StringStorage
MethodTable: 653cdd38
EEClass: 6540eea4
Size: 44(0x2c) bytes
Fields:
MT Type Value Name
653c3e94 ...m.Data.DataColumn 013b82fc Column
653c46d8 ...em.Data.DataTable 013b6120 Table
79101058 System.Type 013b8390 DataType
653e2d80 System.Int32 18 StorageTypeCode
79166df8 ...lections.BitArray 00000000 dbNullBits
790f9c18 System.Object 790d6584 DefaultValue
790f9c18 System.Object 013b846c NullValue
79104f64 System.Boolean 0 IsCloneable
79104f64 System.Boolean 0 IsCustomDefinedType
79104f64 System.Boolean 1 IsStringType
79104f64 System.Boolean 0 IsValueType
79124228 System.Object[] 013b8478 StorageClassType
79124228 System.Object[] 013b9008 values
看到values数组,应该就在这里了:
0:000> !dumparray -details 013b9008
Name: System.String[]
MethodTable: 79124228
EEClass: 7912479c
Size: 528(0x210) bytes
Array: Rank 1, Number of elements 128, Type CLASS
Element Methodtable: 790fa3e0
下面输出的具体的数组里面的每个项,不知道为什么多了几百个null,就选择前两个显示出来:
[0] 013b60b0
Name: System.String
MethodTable: 790fa3e0
EEClass: 790fa340
Size: 26(0x1a) bytes
String: aaaa
Fields:
MT Type Value Name
790fed1c System.Int32 5 m_arrayLength
790fed1c System.Int32 4 m_stringLength
790fbefc System.Char 61 m_firstChar
790fa3e0 System.String static Empty
>> Domain:Value 00154598:790d6584 <<
>> Domain:Value 00154598:013b16e8 <<
[1] 013b60e8
Name: System.String
MethodTable: 790fa3e0
EEClass: 790fa340
Size: 26(0x1a) bytes
String: cccc
Fields:
MT Type Value Name
790fed1c System.Int32 5 m_arrayLength
790fed1c System.Int32 4 m_stringLength
790fbefc System.Char 63 m_firstChar
790fa3e0 System.String static Empty
>> Domain:Value 00154598:790d6584 <<
>> Domain:Value 00154598:013b16e8 <<
呵呵,看到了久违的aaaa和cccc,在最上面构在的DataTable里面的Column1里面。
节目的最后,在网上找到了一个Johan Olofsson的Windbg Script直接传递两个参数可以打印出相应的DataTable的第几个第几个Column:
.foreach
( value{
!do poi(poi(poi(poi(poi(poi(${$arg1}+0x18)+0x8)+0x4)+0xc+0x4*${$arg2})+0x48)+0x10) -v -short
})
{
!do ${value}
}
使用的时候很简单:
$$>a< dumptablevalues 0x0a8cdaa8 0
这样就可以了。Dumptablevalues是你保存的windbg.exe相同目录下面的文件名。0x0a8cdaa8是DataTable的address。0是你想要显示的第几列。
在快速查看DataTable里面的Column的时候比较有用。
Script很简单,poi是取值的意思,里面的一系列十六进制的数字,就是在windbg输出的时候的Offset列的东西。每个数字对应者上面的一步一步的对象和层级关系。
另外,文中展示的windbg结果,为了显示排版方便,去掉了一些无关紧要的行和列。
Ok,到这里吧,就到这里吧。