Repository navigation
ICorDebugILCode::GetEHClauses returns no clauses for a ReJIT IL body with a small-format EH section #135919
Description
TL;DR:
ICorDebugILCode::GetEHClauseson ReJIT IL reports zero clauses when the EH section uses the small format, and can report clauses for a tiny-header method that has none. The cause is four defects inCordbReJitILCode::Init, which I reproduced on the shipped 10.0.12 runtime and on main, on linux-x64, windows-x64 and osx-arm64. Any debugger that reads the EH clauses of ReJIT IL is affected.
Description
CordbReJitILCode::Init (src/coreclr/debug/di/module.cpp) mis-reads ReJIT IL bodies in two ways.
- If the exception-handling section uses the small format (ECMA-335 II.25.4.6),
ICorDebugILCode::GetEHClausesreports zero clauses. The same clauses in the fat format are reported correctly. - If the method has a tiny header, the debugger can report EH clauses for a method that has none.
This affects the ICorDebugILCode returned by both ICorDebugFunction3::GetActiveReJitRequestILCode and ICorDebugILFrame4::GetCodeEx(ILCODE_REJIT_IL). I reproduced both problems on the shipped 10.0.12 runtime (linux-x64, windows-x64 and osx-arm64) and on current main. I exercised both entry points for the small-format problem, and GetActiveReJitRequestILCode for the tiny-header problem.
There are four defects. Line numbers are at b452b42.
The first three are in the small-format branch (L3356-L3390):
ULONG32 m_cClauses = (sectionHeader.DataSize - 4) / sizeof(IMAGE_COR_ILMETHOD_SECT_SMALL);
...
TargetBuffer buffer(ehClauseHeader + sizeof(IMAGE_COR_ILMETHOD_SECT_SMALL), m_cClauses*sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_SMALL));- The count is stored in a local, not the member.
ULONG32 m_cClausesdeclares a local that shadows the member (L3361). The member stays at the 0 set in the constructor (L3229), and that member is whatGetEHClausesreturns (L3427). - The divisor is the 2-byte section header, not the 12-byte clause.
sizeof(IMAGE_COR_ILMETHOD_SECT_SMALL)is 2 (corhdr.h), whileIMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_SMALLis 12 (corhdr.h). The count comes out six times too large, so the debugger reads about six times too much target memory. If that memory is not readable,Initfails. - The clauses are read from the wrong offset. The clauses follow a 2-byte
Reservedfield, so they start 4 bytes into the section (corhdr.h). The read starts at +2.
The fourth defect is in the section check before the small-format branch:
- A tiny header is treated as if it had section flags. The check for an EH section (L3308) tests
CorILMethod_MoreSects(0x08) against the first header byte even when the header is tiny. A tiny header has no section flags: in a tiny header, the 0x08 bit is bit 1 of the code size. A tiny body whose code size is 2, 3, 6, 7, … is therefore treated as having an extra section, and the debugger reads, and may parse, whatever follows the code.COR_ILMETHOD_TINY::GetSectincorhlpr.hreturns no section for a tiny header (L466-L469).
The branch for fat EH sections uses the right clause size and starting offset.
Because the small-format branch leaves the member's clause count at 0, GetEHClauses never exposes the clauses that branch decodes wrongly, although the over-read still happens and can make Init fail. The same zero count hides the bogus clauses parsed from bytes after a tiny body when those bytes look like a small EH section. Fixing only the small-format branch would therefore expose those bogus clauses, so the tiny-header check needs fixing too. The code with these four defects has not changed since the repository was first imported in 2015.
Who is affected
Any debugger that reads the EH clauses of ReJIT IL. A ReJIT version has a small EH section in either of two cases:
- The profiler does not call
SetILFunctionBody. The version then uses the method's default IL, which is its original IL unless something has replaced it (rejit.cpp, codeversion.cpp). Roslyn writes small EH headers whenever the regions fit. This covers profilers that ReJIT only to change codegen flags, and inliners ReJITted by the runtime. - The profiler supplies a small section itself. For example,
SectEH_Emitincorhlpr.cpppicks the small format when the clauses fit.
Edit and Continue versions are not affected: the DAC only hands this code IL versions whose source is ReJIT (dacdbiimpl.cpp).
Reproduction
I wrote three small programs:
- a C# app whose method
Targethas onetry/catch; Roslyn emits it with a small EH section (DataSize 16, one clause: try 0+54, handler 54+5); - a profiler that calls
RequestReJITonTarget. ItsGetReJITParametersonly callsSetCodegenFlags, so the ReJIT version uses the method's original IL; - an ICorDebug client that attaches and calls
GetActiveReJitRequestILCodeand thenGetEHClauses. It also walks to a thread parked insideTargetand callsGetCodeEx(ILCODE_REJIT_IL)and thenGetEHClauses.
Two variants act as a control and as the tiny-header case:
- The control has the profiler pass the same body to
SetILFunctionBodywith the EH section re-encoded in the fat format. - The tiny-header case ReJITs a two-byte method (
ldc.i4.7; ret, tiny header byte 0x0A). The profiler passes its body followed by bytes shaped like a fat EH section. The runtime ignores anything after a tiny body.
These are the results with the shipped 10.0.12 runtime and its own mscordbi. All three platforms give identical results. Both entry points agree for the first two rows; the tiny-body row was measured through GetActiveReJitRequestILCode only, because no frame of the tiny method exists.
| ReJIT body | Expected | linux-x64 | windows-x64 | osx-arm64 |
|---|---|---|---|---|
| original IL, small EH section | 1 clause | 0 | 0 | 0 |
| same clause, fat EH section (control) | 1 clause | 1 | 1 | 1 |
| tiny body + bytes shaped like a fat EH section | 0 clauses | 1 (bogus: try 0+1, handler 1+1) | 1 | 1 |
Fix
I have a fix and will open a PR. It does four things:
- assigns the member;
- divides by
sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_SMALL); - reads the clauses from
offsetof(IMAGE_COR_ILMETHOD_SECT_EH_SMALL, Clauses); - checks
CorILMethod_MoreSectsonly for fat headers.
With an mscordbi built from the fix, all three rows above come out as expected on all three platforms.
Note
This issue was prepared with AI assistance (Anthropic Claude and OpenAI Codex), under my direction. I reviewed it before posting.
Activity
dotnet-policy-service commentedon Oct 16, 2026
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.
Fix CordbReJitILCode reading small-format EH sections and tiny headers (