Skip to content

[cDAC] Fix bug in GetThreadData#126592

Open
rcj1 wants to merge 1 commit intodotnet:mainfrom
rcj1:fix-thread-state-bug
Open

[cDAC] Fix bug in GetThreadData#126592
rcj1 wants to merge 1 commit intodotnet:mainfrom
rcj1:fix-thread-state-bug

Conversation

@rcj1
Copy link
Copy Markdown
Contributor

@rcj1 rcj1 commented Apr 6, 2026

We have to ensure that the thread is reported as dead when either Dead or ReportDead are set. There are, however, certain cases where it is useful to distinguish between the two, for example GetThreadOwningMonitorLock.

Copilot AI review requested due to automatic review settings April 6, 2026 23:14
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes cDAC thread “dead” semantics by treating ThreadState.ReportDead as dead for enumeration and IsThreadMarkedDead, while still allowing callers to distinguish Dead vs ReportDead when needed (e.g., monitor-lock ownership queries).

Changes:

  • Update thread enumeration and IsThreadMarkedDead to consider ReportDead as dead.
  • Extend the managed thread contract to surface ReportDead and convert runtime state bits into contract flags.
  • Adjust Thread contract documentation to reflect that raw runtime state requires conversion to the contract enum.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs Treat ReportDead threads as dead for enumeration and IsThreadMarkedDead.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs Add mapping from runtime Thread::State bits (incl. TS_ReportDead) into contract ThreadState.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs Add ThreadState.ReportDead to the public contract enum.
docs/design/datacontracts/Thread.md Note that Thread::State should be converted to the contract enum rather than used as a raw integer.

Background = 0x00000200, // Thread is a background thread
Unstarted = 0x00000400, // Thread has never been started
Dead = 0x00000800, // Thread is dead
ReportDead = 0x00001000, // Thread is dead for the purposes of the debugger
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThreadState.ReportDead is assigned 0x00001000, but the runtime’s Thread::TS_ReportDead flag is 0x00010000 (see src/coreclr/vm/threads.h). This mismatch is error-prone for consumers and also conflicts with the native TS_WeOwn bit at 0x00001000. Update the contract value to 0x00010000 to match the runtime flag (and keep ThreadState values aligned with Thread::ThreadState).

Suggested change
ReportDead = 0x00001000, // Thread is dead for the purposes of the debugger
ReportDead = 0x00010000, // Thread is dead for the purposes of the debugger

Copilot uses AI. Check for mistakes.
@@ -339,7 +339,7 @@ public int EnumerateThreads(nint fpCallback, nint pUserData)
{
Contracts.ThreadData threadData = threadContract.GetThreadData(currentThread);
// Match native: skip dead and unstarted threads
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says “skip dead and unstarted threads”, but the condition now also skips ReportDead. Update the comment to reflect the actual filter so future readers don’t miss that ReportDead threads are excluded too.

Suggested change
// Match native: skip dead and unstarted threads
// Match native: skip dead, report-dead, and unstarted threads

Copilot uses AI. Check for mistakes.
Comment on lines 390 to 394
try
{
Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread));
*pResult = (threadData.State & Contracts.ThreadState.Dead) != 0 ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
*pResult = (threadData.State & (Contracts.ThreadState.Dead | Contracts.ThreadState.ReportDead)) != 0 ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the semantics of IsThreadMarkedDead to treat ReportDead as dead. There are existing dump tests that cross-validate this API against the contract’s ThreadState.Dead bit only; those should be updated to use (Dead | ReportDead) so the test reflects the new intended behavior and prevents regressions.

Copilot uses AI. Check for mistakes.
Id: target.Read<uint>(address + /* Thread::Id offset */),
OSId: target.ReadNUInt(address + /* Thread::OSId offset */),
State: target.Read<uint>(address + /* Thread::State offset */),
State: target.Read<uint>(address + /* Thread::State offset */) /* -> convert to contract enum */,
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The contract documentation now notes that the raw Thread::State value should be converted to the contract enum, but ThreadState earlier in this doc is still missing the newly added ReportDead flag. Please update the documented ThreadState enum and/or add a brief note describing how TS_ReportDead maps to ThreadState.ReportDead so the doc matches the contract surface area.

Suggested change
State: target.Read<uint>(address + /* Thread::State offset */) /* -> convert to contract enum */,
State: target.Read<uint>(address + /* Thread::State offset */) /* -> convert to contract enum; for example, TS_ReportDead maps to ThreadState.ReportDead */,

Copilot uses AI. Check for mistakes.
Background = 0x00000200, // Thread is a background thread
Unstarted = 0x00000400, // Thread has never been started
Dead = 0x00000800, // Thread is dead
ReportDead = 0x00001000, // Thread is dead for the purposes of the debugger
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does it mean "for the purpose of the debugger"?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants