@@ -57,13 +57,14 @@ public sealed class ETWProfileDataProvider : IProfileDataProvider, IDisposable {
5757 // known module loaded in the process. This can be dynamically generated code
5858 // (e.g., LUA JIT, Java JIT), code from unloaded modules, or other unmapped regions.
5959 private const string UnknownModuleName = "[Unknown Module]" ;
60- private sealed class UnknownModuleState ( ProfileImage image , ProfileModuleBuilder moduleBuilder , ProfileData profileData ) {
60+ private sealed class UnknownModuleState ( ProfileImage image , ProfileData profileData ) {
6161 private readonly object lock_ = new ( ) ;
6262 private readonly ConcurrentDictionary < int , ( IRTextFunction Function , FunctionDebugInfo DebugInfo ) > threadFunctions_ = new ( ) ;
6363 private readonly ProfileData profileData_ = profileData ;
64+ private readonly ILoadedDocument document_ = LoadedDocument . CreateDummyDocument ( UnknownModuleName ) ;
6465
6566 public ProfileImage Image { get ; } = image ;
66- public ProfileModuleBuilder ModuleBuilder { get ; } = moduleBuilder ;
67+ public ILoadedDocument Document => document_ ;
6768
6869 /// <summary>
6970 /// Gets or creates a synthetic function for samples with unmapped IPs
@@ -83,17 +84,17 @@ private sealed class UnknownModuleState(ProfileImage image, ProfileModuleBuilder
8384 return existing ;
8485 }
8586
86- var thread = profileData_ . FindThread ( threadId ) ;
87+ ProfileThread thread = profileData_ . FindThread ( threadId ) ;
8788 string startModule = ResolveStartModule ( thread ) ;
8889 string funcName = FormatFuncName ( threadId , thread ? . Name , startModule ) ;
8990
9091 // RVA is set to a non-zero value (threadId) so FunctionDebugInfo.IsUnknown
9192 // returns false, and the frame's RVA matches so offset computes to 0.
9293 // Else, IsUnknown is dep on RVA==0 and Size==0, so this is somewhat a
9394 // hack workaround (though similar is used elsewhere in codebase).
94- var debugInfo = new FunctionDebugInfo ( funcName , threadId , 1 ) ;
95- var func = ModuleBuilder . ModuleDocument . AddDummyFunction ( funcName ) ;
96- var result = ( func , debugInfo ) ;
95+ FunctionDebugInfo debugInfo = new FunctionDebugInfo ( funcName , threadId , 1 ) ;
96+ IRTextFunction func = document_ . AddDummyFunction ( funcName ) ;
97+ ( IRTextFunction func , FunctionDebugInfo debugInfo ) result = ( func , debugInfo ) ;
9798 threadFunctions_ [ threadId ] = result ;
9899 return result ;
99100 }
@@ -640,6 +641,7 @@ private async Task<ResolvedProfileStack> ProcessUnresolvedStackAsync(ProfileStac
640641 int managedFrames = 0 ;
641642 int unknownFrames = 0 ;
642643 int resolvedFrames = 0 ;
644+ bool prevFrameWasUnknownJit = false ;
643645
644646 //? TODO: Stacks with >256 frames are truncated, inclusive time computation is not right then
645647 //? for ex it never gets to main. Easy example is a quicksort impl
@@ -671,18 +673,21 @@ private async Task<ResolvedProfileStack> ProcessUnresolvedStackAsync(ProfileStac
671673 if ( frameImage == null ) {
672674 unknownFrames ++ ;
673675 // IP doesn't map to any known module (e.g., JITted). Attribute
674- // to a synthetic module so the work is visible in the profile.
675- var unknownState = GetUnknownModule ( context . ProcessId ) ;
676- if ( unknownState != null ) {
677- ( IRTextFunction Function , FunctionDebugInfo DebugInfo ) = unknownState . GetOrCreateThreadFunction ( context . ThreadId ) ;
678- ResolvedProfileStackFrameKey unknownFrame = new ResolvedProfileStackFrameKey ( DebugInfo , unknownState . Image , false ) ;
679- resolvedStack . AddFrame ( Function , frameIp , DebugInfo . RVA ,
680- frameIndex , unknownFrame , stack , pointerSize ) ;
681- continue ;
676+ // to a synthetic JIT function, but skip consecutive unknown frames
677+ // to avoid recursive self-call chains in the call tree.
678+ if ( ! prevFrameWasUnknownJit ) {
679+ var unknownState = GetUnknownModule ( context . ProcessId ) ;
680+ if ( unknownState != null ) {
681+ ( IRTextFunction Function , FunctionDebugInfo DebugInfo ) = unknownState . GetOrCreateThreadFunction ( context . ThreadId ) ;
682+ ResolvedProfileStackFrameKey unknownFrame = new ResolvedProfileStackFrameKey ( DebugInfo , unknownState . Image , false ) ;
683+ resolvedStack . AddFrame ( Function , frameIp , DebugInfo . RVA ,
684+ frameIndex , unknownFrame , stack , pointerSize ) ;
685+ prevFrameWasUnknownJit = true ;
686+ continue ;
687+ }
682688 }
683689
684- resolvedStack . AddFrame ( null , frameIp , 0 , frameIndex , ResolvedProfileStackFrameKey . Unknown , stack ,
685- pointerSize ) ;
690+ // Consecutive unknown frame or no UnknownModule state -- skip.
686691 continue ;
687692 }
688693 }
@@ -731,6 +736,7 @@ private async Task<ResolvedProfileStack> ProcessUnresolvedStackAsync(ProfileStac
731736 resolvedStack . AddFrame ( funcPair . Function , frameIp , frameRva , frameIndex ,
732737 resolvedFrame , stack , pointerSize ) ;
733738 resolvedFrames ++ ;
739+ prevFrameWasUnknownJit = false ; // Known frame breaks unknown frame run.
734740 }
735741
736742 var totalTime = sw . Elapsed ;
@@ -746,8 +752,7 @@ private async Task<ResolvedProfileStack> ProcessUnresolvedStackAsync(ProfileStac
746752
747753 /// <summary>
748754 /// Pre-creates the synthetic [Unknown Module] for a process. Must be called
749- /// single-threaded before parallel sample processing begins, since
750- /// RawProfileData.AddImageToProcess is not thread-safe.
755+ /// before parallel sample processing begins.
751756 /// </summary>
752757 private void PreCreateUnknownModule ( RawProfileData rawProfile , int processId ) {
753758 ProfileImage image = new ProfileImage (
@@ -763,14 +768,8 @@ private void PreCreateUnknownModule(RawProfileData rawProfile, int processId) {
763768 rawProfile . AddImageToProcess ( processId , image ) ;
764769 profileData_ . Modules [ image . Id ] = image ;
765770
766- ProfileModuleBuilder moduleBuilder = new ProfileModuleBuilder ( report_ , compilerInfoProvider_ ) ;
767- moduleBuilder . ModuleDocument = LoadedDocument . CreateDummyDocument ( UnknownModuleName ) ;
768- moduleBuilder . Summary = moduleBuilder . ModuleDocument . Summary ;
769- moduleBuilder . Initialized = true ;
770-
771- UnknownModuleState state = new UnknownModuleState ( image , moduleBuilder , profileData_ ) ;
771+ UnknownModuleState state = new UnknownModuleState ( image , profileData_ ) ;
772772 unknownModules_ [ processId ] = state ;
773- imageModuleMap_ . TryAdd ( image . Id , moduleBuilder ) ;
774773
775774 Trace . WriteLine ( $ "Pre-created synthetic { UnknownModuleName } for process { processId } , ImageId={ image . Id } ") ;
776775 }
@@ -814,6 +813,12 @@ private ILoadedDocument FindSessionDocuments(string imageName, out List<ILoadedD
814813 }
815814 }
816815
816+ // Include synthetic [Unknown Module] documents so the UI can
817+ // navigate to JIT functions via FindLoadedDocument.
818+ foreach ( var state in unknownModules_ . Values ) {
819+ otherDocuments . Add ( state . Document ) ;
820+ }
821+
817822 return exeDocument ;
818823 }
819824
0 commit comments