11using BenchmarkDotNet . Filters ;
22using Iced . Intel ;
33using Microsoft . Diagnostics . Runtime ;
4+ using Microsoft . Diagnostics . Runtime . Utilities ;
45using System ;
56using System . Collections . Generic ;
7+ using System . IO ;
68using System . Linq ;
79using System . Text . RegularExpressions ;
810
@@ -66,16 +68,29 @@ private static void FilterAndEnqueue(State state, Settings settings)
6668
6769 foreach ( ClrModule module in state . Runtime . EnumerateModules ( ) )
6870 foreach ( ClrType type in module . EnumerateTypeDefToMethodTableMap ( ) . Select ( map => state . Runtime . GetTypeByMethodTable ( map . MethodTable ) ) . Where ( type => type is not null ) )
69- foreach ( ClrMethod method in type . Methods . Where ( method => CanBeDisassembled ( method ) && method . Signature != null ) )
70- foreach ( Regex filter in filters )
71+ foreach ( ClrMethod method in type . Methods . Where ( method => method . Signature != null ) )
72+ {
73+ if ( method . NativeCode > 0 )
7174 {
72- if ( filter . IsMatch ( method . Signature ) )
75+ if ( ! state . AddressToNameMapping . TryGetValue ( method . NativeCode , out _ ) )
7376 {
74- state . Todo . Enqueue ( new MethodInfo ( method ,
75- depth : settings . MaxDepth ) ) ; // don't allow for recursive disassembling
76- break ;
77+ state . AddressToNameMapping . Add ( method . NativeCode , method . Signature ) ;
7778 }
7879 }
80+
81+ if ( CanBeDisassembled ( method ) )
82+ {
83+ foreach ( Regex filter in filters )
84+ {
85+ if ( filter . IsMatch ( method . Signature ) )
86+ {
87+ state . Todo . Enqueue ( new MethodInfo ( method ,
88+ depth : settings . MaxDepth ) ) ; // don't allow for recursive disassembling
89+ break ;
90+ }
91+ }
92+ }
93+ }
7994 }
8095
8196 private static DisassembledMethod [ ] Disassemble ( Settings settings , State state )
@@ -96,8 +111,7 @@ private static DisassembledMethod[] Disassemble(Settings settings, State state)
96111 return result . ToArray ( ) ;
97112 }
98113
99- private static bool CanBeDisassembled ( ClrMethod method )
100- => ! ( method . ILOffsetMap . Length == 0 && ( method . HotColdInfo . HotStart == 0 || method . HotColdInfo . HotSize == 0 ) ) ;
114+ private static bool CanBeDisassembled ( ClrMethod method ) => method . ILOffsetMap . Length > 0 && method . NativeCode > 0 ;
101115
102116 private static DisassembledMethod DisassembleMethod ( MethodInfo methodInfo , State state , Settings settings )
103117 {
@@ -128,9 +142,10 @@ private static DisassembledMethod DisassembleMethod(MethodInfo methodInfo, State
128142 codes . AddRange ( uniqueSourceCodeLines ) ;
129143 }
130144
131- // for getting ASM we try to use data from HotColdInfo if available (better for decoding)
132- foreach ( var map in GetCompleteNativeMap ( method ) )
133- codes . AddRange ( Decode ( map . StartAddress , ( uint ) ( map . EndAddress - map . StartAddress ) , state , methodInfo . Depth , method ) ) ;
145+ foreach ( var map in GetCompleteNativeMap ( method , state . Runtime ) )
146+ {
147+ codes . AddRange ( Decode ( map , state , methodInfo . Depth , method ) ) ;
148+ }
134149
135150 Map [ ] maps = settings . PrintSource
136151 ? codes . GroupBy ( code => code . InstructionPointer ) . OrderBy ( group => group . Key ) . Select ( group => new Map ( ) { SourceCodes = group . ToArray ( ) } ) . ToArray ( )
@@ -144,14 +159,25 @@ private static DisassembledMethod DisassembleMethod(MethodInfo methodInfo, State
144159 } ;
145160 }
146161
147- private static IEnumerable < Asm > Decode ( ulong startAddress , uint size , State state , int depth , ClrMethod currentMethod )
162+ private static IEnumerable < Asm > Decode ( ILToNativeMap map , State state , int depth , ClrMethod currentMethod )
148163 {
164+ ulong startAddress = map . StartAddress ;
165+ uint size = ( uint ) ( map . EndAddress - map . StartAddress ) ;
166+
149167 byte [ ] code = new byte [ size ] ;
150- int bytesRead = state . Runtime . DataTarget . DataReader . Read ( startAddress , code ) ;
151- if ( bytesRead == 0 || bytesRead != size )
152- yield break ;
153168
154- var reader = new ByteArrayCodeReader ( code , 0 , bytesRead ) ;
169+ int totalBytesRead = 0 ;
170+ do
171+ {
172+ int bytesRead = state . Runtime . DataTarget . DataReader . Read ( startAddress + ( ulong ) totalBytesRead , new Span < byte > ( code , totalBytesRead , ( int ) size - totalBytesRead ) ) ;
173+ if ( bytesRead <= 0 )
174+ {
175+ throw new EndOfStreamException ( $ "Tried to read { size } bytes for { currentMethod . Signature } , got only { totalBytesRead } ") ;
176+ }
177+ totalBytesRead += bytesRead ;
178+ } while ( totalBytesRead != size ) ;
179+
180+ var reader = new ByteArrayCodeReader ( code , 0 , ( int ) size ) ;
155181 var decoder = Decoder . Create ( state . Runtime . DataTarget . DataReader . PointerSize * 8 , reader ) ;
156182 decoder . IP = startAddress ;
157183
@@ -204,7 +230,15 @@ private static void TryTranslateAddressToName(Instruction instruction, State sta
204230 if ( method is null && ( address & ( ( uint ) runtime . DataTarget . DataReader . PointerSize - 1 ) ) == 0 )
205231 {
206232 if ( runtime . DataTarget . DataReader . ReadPointer ( address , out ulong newAddress ) && newAddress > ushort . MaxValue )
233+ {
207234 method = runtime . GetMethodByInstructionPointer ( newAddress ) ;
235+
236+ method = WorkaroundGetMethodByInstructionPointerBug ( runtime , method , newAddress ) ;
237+ }
238+ }
239+ else
240+ {
241+ method = WorkaroundGetMethodByInstructionPointerBug ( runtime , method , address ) ;
208242 }
209243
210244 if ( method is null )
@@ -222,6 +256,13 @@ private static void TryTranslateAddressToName(Instruction instruction, State sta
222256 state . AddressToNameMapping . Add ( address , methodName ) ;
223257 }
224258
259+ // GetMethodByInstructionPointer sometimes returns wrong methods.
260+ // In case given address does not belong to the methods range, null is returned.
261+ private static ClrMethod WorkaroundGetMethodByInstructionPointerBug ( ClrRuntime runtime , ClrMethod method , ulong newAddress )
262+ => TryReadNativeCodeAddresses ( runtime , method , out ulong startAddress , out ulong endAddress ) && ! ( startAddress >= newAddress && newAddress <= endAddress )
263+ ? null
264+ : method ;
265+
225266 internal static bool TryGetReferencedAddress ( Instruction instruction , uint pointerSize , out ulong referencedAddress )
226267 {
227268 for ( int i = 0 ; i < instruction . OpCount ; i ++ )
@@ -255,26 +296,52 @@ internal static bool TryGetReferencedAddress(Instruction instruction, uint point
255296 return false ;
256297 }
257298
258- private static ILToNativeMap [ ] GetCompleteNativeMap ( ClrMethod method )
299+ private static ILToNativeMap [ ] GetCompleteNativeMap ( ClrMethod method , ClrRuntime runtime )
300+ {
301+ if ( ! TryReadNativeCodeAddresses ( runtime , method , out ulong startAddress , out ulong endAddress ) )
302+ {
303+ startAddress = method . NativeCode ;
304+ endAddress = ulong . MaxValue ;
305+ }
306+
307+ ILToNativeMap [ ] sortedMaps = method . ILOffsetMap // CanBeDisassembled ensures that there is at least one map in ILOffsetMap
308+ . Where ( map => map . StartAddress >= startAddress && map . StartAddress < endAddress ) // can be false for Tier 0 maps, EndAddress is not checked on purpose here
309+ . Where ( map => map . StartAddress < map . EndAddress ) // some maps have 0 length (they don't have corresponding assembly code?)
310+ . OrderBy ( map => map . StartAddress ) // we need to print in the machine code order, not IL! #536
311+ . Select ( map => new ILToNativeMap ( )
312+ {
313+ StartAddress = map . StartAddress ,
314+ // some maps have EndAddress > codeHeaderData.MethodStart + codeHeaderData.MethodSize and contain garbage (#2074). They need to be fixed!
315+ EndAddress = Math . Min ( map . EndAddress , endAddress ) ,
316+ ILOffset = map . ILOffset
317+ } )
318+ . ToArray ( ) ;
319+
320+ if ( sortedMaps . Length == 0 )
321+ {
322+ // In such situation ILOffsetMap most likely describes Tier 0, while CodeHeaderData Tier 1.
323+ // Since we care about Tier 1 (if it's present), we "fake" a Tier 1 map.
324+ return new [ ] { new ILToNativeMap ( ) { StartAddress = startAddress , EndAddress = endAddress } } ;
325+ }
326+
327+ return sortedMaps ;
328+ }
329+
330+ private static bool TryReadNativeCodeAddresses ( ClrRuntime runtime , ClrMethod method , out ulong startAddress , out ulong endAddress )
259331 {
260- // it's better to use one single map rather than few small ones
261- // it's simply easier to get next instruction when decoding ;)
262- var hotColdInfo = method . HotColdInfo ;
263- if ( hotColdInfo . HotSize > 0 && hotColdInfo . HotStart > 0 )
332+ if ( method is not null
333+ && runtime . DacLibrary . SOSDacInterface . GetCodeHeaderData ( method . NativeCode , out var codeHeaderData ) == HResult . S_OK
334+ && codeHeaderData . MethodSize > 0 ) // false for extern methods!
264335 {
265- return hotColdInfo . ColdSize <= 0
266- ? new [ ] { new ILToNativeMap ( ) { StartAddress = hotColdInfo . HotStart , EndAddress = hotColdInfo . HotStart + hotColdInfo . HotSize , ILOffset = - 1 } }
267- : new [ ]
268- {
269- new ILToNativeMap ( ) { StartAddress = hotColdInfo . HotStart , EndAddress = hotColdInfo . HotStart + hotColdInfo . HotSize , ILOffset = - 1 } ,
270- new ILToNativeMap ( ) { StartAddress = hotColdInfo . ColdStart , EndAddress = hotColdInfo . ColdStart + hotColdInfo . ColdSize , ILOffset = - 1 }
271- } ;
336+ // HotSize can be missing or be invalid (https://github.com/microsoft/clrmd/issues/1036).
337+ // So we fetch the method size on our own.
338+ startAddress = codeHeaderData . MethodStart ;
339+ endAddress = codeHeaderData . MethodStart + codeHeaderData . MethodSize ;
340+ return true ;
272341 }
273342
274- return method . ILOffsetMap
275- . Where ( map => map . StartAddress < map . EndAddress ) // some maps have 0 length?
276- . OrderBy ( map => map . StartAddress ) // we need to print in the machine code order, not IL! #536
277- . ToArray ( ) ;
343+ startAddress = endAddress = 0 ;
344+ return false ;
278345 }
279346
280347 private static DisassembledMethod CreateEmpty ( ClrMethod method , string reason )
0 commit comments