-
Notifications
You must be signed in to change notification settings - Fork 5.4k
[Async v2] Support Environment.StackTrace #125396
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
tommcdon
wants to merge
34
commits into
dotnet:main
Choose a base branch
from
tommcdon:dev/tommcdon/env_stacktrace
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 30 commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
5e83373
Async v2 callstack continuations for Env.StackTrace
tommcdon bb68d1f
Add Environment.StackTrace test for async v2 continuation tracking
tommcdon 066587a
NativeAOT: augment Environment.StackTrace with async v2 continuations
tommcdon 49a704a
Fix async v2 continuation tracking bugs and update tests
tommcdon 8373fa9
Use CoreLib binder and ContinuationObject for async continuation extr…
tommcdon 0f9e93f
Address PR review feedback: use binder field offsets, remove dead par…
tommcdon 7b8f464
Use DEFINE_CLASS_U/DEFINE_FIELD_U for AsyncDispatcherInfo layout vali…
tommcdon 9d34bab
Fix DWORD truncation and redundant null checks in debugdebugger.cpp
tommcdon b6e06c9
Fix NativeAOT async stack trace stitching and add test
tommcdon c0285e0
Address PR feedback: restructure NativeAOT async stack stitching
tommcdon cd24c9c
Harden debugdebugger.cpp async continuation extraction
tommcdon 7638105
Address PR feedback: use MethodDesc comparison and fix NativeAOT asyn…
tommcdon 3a5cf88
Remove unused using Internal.Runtime.Augments
tommcdon 1fcee72
Improve env-stacktrace test: use Task.Delay(1) and move diagnostics t…
tommcdon 9e5c641
Use HasSameMethodDefAs for DispatchContinuations check
tommcdon eb7a632
Use direct OBJECTREF-to-CONTINUATIONREF cast in ExtractContinuationData
tommcdon 3271f8f
Add async frame hiding for Environment.StackTrace with tri-state config
tommcdon a531119
Clarify mode 0 description: show all frames with async stitching active
tommcdon 7ce029f
Change stacktrace comments to clarify terminology for runtime async
tommcdon 2fa0716
Rename IsAsyncV2Method to IsAsyncMethod in NativeAOT StackFrame
tommcdon 7c0a03e
Add DOTNET_HideAsyncDispatchFrames mode 3: physical-only stack traces
tommcdon 3d3507a
Centralize async dispatch boundary method names in NativeAOT
tommcdon a490632
Add FlagsMask constant and document RVA bit packing safety
tommcdon b862e40
Move IsAsyncMethod to separate field to avoid ARM32 THUMB bit collision
tommcdon 3906c83
Only collect continuations from the innermost async dispatcher
tommcdon 008c77f
Avoid small allocation if no continuations are available
tommcdon 7e21299
Pack IsAsyncMethod into RVA bit 0 with THUMB-safe alignment masking
tommcdon 3a529c4
Clarify mode 0 description for HideAsyncDispatchFrames
tommcdon 487a268
Update inline comments for mode 0 to match config description
tommcdon 2d33385
Cache DispatchContinuations MethodDesc, eliminate duplicate env read,…
tommcdon 96dd789
Address PR feedback: rename config, revert caching, remove null checks
tommcdon b5d9df4
Replace name-based async dispatch boundary detection with RVA/address…
tommcdon e8d00d1
Do not apply async frame hiding to exception stack traces
tommcdon 7e2cfb7
Remove config mode 2 (truncate trailing non-async frames)
tommcdon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -23,7 +23,62 @@ private void InitializeForCurrentThread(int skipFrames, bool needFileInfo) | |||||
| IntPtr[] stackTrace = new IntPtr[frameCount]; | ||||||
| int trueFrameCount = RuntimeImports.RhGetCurrentThreadStackTrace(stackTrace); | ||||||
| Debug.Assert(trueFrameCount == frameCount); | ||||||
| InitializeForIpAddressArray(stackTrace, skipFrames + SystemDiagnosticsStackDepth, frameCount, needFileInfo); | ||||||
|
|
||||||
| int adjustedSkip = skipFrames + SystemDiagnosticsStackDepth; | ||||||
|
|
||||||
| // Read the config to determine async behavior before collecting continuations. | ||||||
| string? envValue = Environment.GetEnvironmentVariable("DOTNET_HideAsyncDispatchFrames"); | ||||||
| int hideMode = envValue switch | ||||||
| { | ||||||
| "0" => 0, | ||||||
| "2" => 2, | ||||||
| "3" => 3, | ||||||
|
|
||||||
| _ => 1, | ||||||
| }; | ||||||
|
tommcdon marked this conversation as resolved.
|
||||||
|
|
||||||
| // Mode 3 (physical only): skip continuation collection entirely. | ||||||
| IntPtr[]? continuationIPs = hideMode == 3 ? null : CollectAsyncContinuationIPs(); | ||||||
| InitializeForIpAddressArray(stackTrace, adjustedSkip, trueFrameCount, needFileInfo, continuationIPs, hideMode); | ||||||
| } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// When executing inside a runtime async (v2) continuation dispatch, collect | ||||||
| /// the DiagnosticIP values from the async continuation chain. | ||||||
| /// Returns null if not inside a dispatch or no valid continuation IPs exist. | ||||||
| /// </summary> | ||||||
| private static unsafe IntPtr[]? CollectAsyncContinuationIPs() | ||||||
| { | ||||||
| AsyncDispatcherInfo* pInfo = AsyncDispatcherInfo.t_current; | ||||||
| if (pInfo is null) | ||||||
| return null; | ||||||
|
|
||||||
| // Only collect continuations from the innermost (first) dispatcher in the chain. | ||||||
| // Outer dispatchers represent already-completed async scopes and are not displayed. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I suspect in many cases the outer dispatchers aren't complete but it doesn't matter because they aren't (necessarily) part of the same async stack. |
||||||
| Continuation? cont = pInfo->NextContinuation; | ||||||
| if (cont is null) | ||||||
| return null; | ||||||
|
|
||||||
| IntPtr[] buffer = new IntPtr[16]; | ||||||
| int count = 0; | ||||||
| while (cont is not null) | ||||||
| { | ||||||
| if (cont.ResumeInfo is not null && cont.ResumeInfo->DiagnosticIP is not null) | ||||||
| { | ||||||
| if (count == buffer.Length) | ||||||
| Array.Resize(ref buffer, buffer.Length * 2); | ||||||
|
|
||||||
| buffer[count++] = (IntPtr)cont.ResumeInfo->DiagnosticIP; | ||||||
| } | ||||||
| cont = cont.Next; | ||||||
| } | ||||||
|
|
||||||
| if (count == 0) | ||||||
| return null; | ||||||
|
|
||||||
| if (count < buffer.Length) | ||||||
| Array.Resize(ref buffer, count); | ||||||
|
|
||||||
| return buffer; | ||||||
| } | ||||||
| #endif | ||||||
|
|
||||||
|
|
@@ -38,42 +93,90 @@ private void InitializeForException(Exception exception, int skipFrames, bool ne | |||||
|
|
||||||
| /// <summary> | ||||||
| /// Initialize the stack trace based on a given array of IP addresses. | ||||||
| /// When continuationIPs is provided, detects the async dispatch boundary | ||||||
| /// during frame construction and splices in continuation frames. | ||||||
| /// </summary> | ||||||
| private void InitializeForIpAddressArray(IntPtr[] ipAddresses, int skipFrames, int endFrameIndex, bool needFileInfo) | ||||||
| private void InitializeForIpAddressArray(IntPtr[] ipAddresses, int skipFrames, int endFrameIndex, bool needFileInfo, IntPtr[]? continuationIPs = null, int hideAsyncDispatchMode = 0) | ||||||
| { | ||||||
| int frameCount = (skipFrames < endFrameIndex ? endFrameIndex - skipFrames : 0); | ||||||
| int continuationCount = continuationIPs?.Length ?? 0; | ||||||
|
|
||||||
| // 0 = show all above dispatch boundary (with async stitching), 1 = hide all non-async after first async, | ||||||
|
tommcdon marked this conversation as resolved.
Outdated
|
||||||
| // 2 = truncate trailing non-async, 3 = physical only (no stitching) | ||||||
|
|
||||||
| // Calculate true frame count upfront - we need to skip EdiSeparators which get | ||||||
| // collapsed onto boolean flags on the preceding stack frame | ||||||
| int outputFrameCount = 0; | ||||||
| // Count physical frames upfront — EdiSeparators are collapsed onto the | ||||||
|
tommcdon marked this conversation as resolved.
Outdated
|
||||||
| // preceding frame's boolean flag and don't produce output frames. | ||||||
| int physicalFrameCount = 0; | ||||||
| for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) | ||||||
| { | ||||||
| if (ipAddresses[frameIndex + skipFrames] != Exception.EdiSeparator) | ||||||
| { | ||||||
| outputFrameCount++; | ||||||
| } | ||||||
| physicalFrameCount++; | ||||||
| } | ||||||
|
|
||||||
| if (outputFrameCount > 0) | ||||||
| int totalCapacity = physicalFrameCount + continuationCount; | ||||||
| if (totalCapacity > 0) | ||||||
| { | ||||||
| _stackFrames = new StackFrame[outputFrameCount]; | ||||||
| _stackFrames = new StackFrame[totalCapacity]; | ||||||
| int outputFrameIndex = 0; | ||||||
| bool asyncFrameSeen = false; | ||||||
|
|
||||||
| for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) | ||||||
| { | ||||||
| IntPtr ipAddress = ipAddresses[frameIndex + skipFrames]; | ||||||
| if (ipAddress != Exception.EdiSeparator) | ||||||
| if (ipAddress == Exception.EdiSeparator) | ||||||
| { | ||||||
| if (outputFrameIndex > 0) | ||||||
| _stackFrames[outputFrameIndex - 1].SetIsLastFrameFromForeignExceptionStackTrace(); | ||||||
| continue; | ||||||
| } | ||||||
|
|
||||||
| var frame = new StackFrame(ipAddress, needFileInfo); | ||||||
|
|
||||||
| if (frame.IsAsyncMethod) | ||||||
| { | ||||||
| _stackFrames[outputFrameIndex++] = new StackFrame(ipAddress, needFileInfo); | ||||||
| asyncFrameSeen = true; | ||||||
| } | ||||||
| else if (outputFrameIndex > 0) | ||||||
|
|
||||||
| // When inside a v2 async dispatch, the DispatchContinuations frame marks | ||||||
| // the boundary between user frames and internal dispatch machinery. | ||||||
| // Truncate there and append the continuation chain instead. | ||||||
| if (continuationIPs is not null && frame.IsAsyncDispatchBoundary()) | ||||||
| { | ||||||
| _stackFrames[outputFrameIndex - 1].SetIsLastFrameFromForeignExceptionStackTrace(); | ||||||
| for (int i = 0; i < continuationCount; i++) | ||||||
| _stackFrames[outputFrameIndex++] = new StackFrame(continuationIPs[i], needFileInfo); | ||||||
| break; | ||||||
| } | ||||||
|
|
||||||
| // Mode 1: hide all non-async frames once we've seen the first async frame. | ||||||
| if (hideAsyncDispatchMode == 1 && asyncFrameSeen && !frame.IsAsyncMethod) | ||||||
| continue; | ||||||
|
|
||||||
| _stackFrames[outputFrameIndex++] = frame; | ||||||
| } | ||||||
| Debug.Assert(outputFrameIndex == outputFrameCount); | ||||||
|
|
||||||
| // Mode 2: trim trailing non-async frames below the last async frame. | ||||||
| if (hideAsyncDispatchMode == 2 && asyncFrameSeen) | ||||||
| { | ||||||
| int lastAsyncIndex = -1; | ||||||
| for (int i = 0; i < outputFrameIndex; i++) | ||||||
| { | ||||||
| if (_stackFrames[i].IsAsyncMethod) | ||||||
| lastAsyncIndex = i; | ||||||
| } | ||||||
| if (lastAsyncIndex >= 0) | ||||||
| outputFrameIndex = lastAsyncIndex + 1; | ||||||
| } | ||||||
|
|
||||||
| if (outputFrameIndex < totalCapacity) | ||||||
| Array.Resize(ref _stackFrames, outputFrameIndex); | ||||||
|
|
||||||
| _numOfFrames = outputFrameIndex; | ||||||
| } | ||||||
| else | ||||||
| { | ||||||
| _numOfFrames = 0; | ||||||
| } | ||||||
|
|
||||||
| _numOfFrames = outputFrameCount; | ||||||
| _methodsToSkip = 0; | ||||||
| } | ||||||
|
|
||||||
|
|
||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.