Skip to content

Commit b8beed2

Browse files
Poison address-exposed user variables in debug (#54685)
* Poison address exposed user variables in debug code Fix #13072 * Run jit-format * Use named scratch register and kill it in LSRA * Enable it unconditionally for testing purposes * Remove unnecessary modified reg on ARM * Fix OSR and get rid of test code * Remove a declaration * Undo modified comment and use modulo instead of and * Add a test * Rephrase comment Co-authored-by: Kunal Pathak <Kunal.Pathak@microsoft.com> * Disable poisoning test on mono * Remove outdated line Co-authored-by: Kunal Pathak <Kunal.Pathak@microsoft.com>
1 parent 09785ef commit b8beed2

File tree

10 files changed

+162
-17
lines changed

10 files changed

+162
-17
lines changed

src/coreclr/jit/codegen.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,8 @@ class CodeGen final : public CodeGenInterface
348348

349349
void genAllocLclFrame(unsigned frameSize, regNumber initReg, bool* pInitRegZeroed, regMaskTP maskArgRegsLiveIn);
350350

351+
void genPoisonFrame(regMaskTP bbRegLiveIn);
352+
351353
#if defined(TARGET_ARM)
352354

353355
bool genInstrWithConstant(

src/coreclr/jit/codegencommon.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12528,3 +12528,65 @@ void CodeGenInterface::VariableLiveKeeper::dumpLvaVariableLiveRanges() const
1252812528
}
1252912529
#endif // DEBUG
1253012530
#endif // USING_VARIABLE_LIVE_RANGE
12531+
12532+
//-----------------------------------------------------------------------------
12533+
// genPoisonFrame: Generate code that places a recognizable value into address exposed variables.
12534+
//
12535+
// Remarks:
12536+
// This function emits code to poison address exposed non-zero-inited local variables. We expect this function
12537+
// to be called when emitting code for the scratch BB that comes right after the prolog.
12538+
// The variables are poisoned using 0xcdcdcdcd.
12539+
void CodeGen::genPoisonFrame(regMaskTP regLiveIn)
12540+
{
12541+
assert(compiler->compShouldPoisonFrame());
12542+
assert((regLiveIn & genRegMask(REG_SCRATCH)) == 0);
12543+
12544+
// The first time we need to poison something we will initialize a register to the largest immediate cccccccc that
12545+
// we can fit.
12546+
bool hasPoisonImm = false;
12547+
for (unsigned varNum = 0; varNum < compiler->info.compLocalsCount; varNum++)
12548+
{
12549+
LclVarDsc* varDsc = compiler->lvaGetDesc(varNum);
12550+
if (varDsc->lvIsParam || varDsc->lvMustInit || !varDsc->lvAddrExposed)
12551+
{
12552+
continue;
12553+
}
12554+
12555+
assert(varDsc->lvOnFrame);
12556+
12557+
if (!hasPoisonImm)
12558+
{
12559+
#ifdef TARGET_64BIT
12560+
genSetRegToIcon(REG_SCRATCH, (ssize_t)0xcdcdcdcdcdcdcdcd, TYP_LONG);
12561+
#else
12562+
genSetRegToIcon(REG_SCRATCH, (ssize_t)0xcdcdcdcd, TYP_INT);
12563+
#endif
12564+
hasPoisonImm = true;
12565+
}
12566+
12567+
// For 64-bit we check if the local is 8-byte aligned. For 32-bit, we assume everything is always 4-byte aligned.
12568+
#ifdef TARGET_64BIT
12569+
bool fpBased;
12570+
int addr = compiler->lvaFrameAddress((int)varNum, &fpBased);
12571+
#else
12572+
int addr = 0;
12573+
#endif
12574+
int size = (int)compiler->lvaLclSize(varNum);
12575+
int end = addr + size;
12576+
for (int offs = addr; offs < end;)
12577+
{
12578+
#ifdef TARGET_64BIT
12579+
if ((offs % 8) == 0 && end - offs >= 8)
12580+
{
12581+
GetEmitter()->emitIns_S_R(ins_Store(TYP_LONG), EA_8BYTE, REG_SCRATCH, (int)varNum, offs - addr);
12582+
offs += 8;
12583+
continue;
12584+
}
12585+
#endif
12586+
12587+
assert((offs % 4) == 0 && end - offs >= 4);
12588+
GetEmitter()->emitIns_S_R(ins_Store(TYP_INT), EA_4BYTE, REG_SCRATCH, (int)varNum, offs - addr);
12589+
offs += 4;
12590+
}
12591+
}
12592+
}

src/coreclr/jit/codegenlinear.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,13 @@ void CodeGen::genCodeForBBlist()
395395
compiler->compCurStmt = nullptr;
396396
compiler->compCurLifeTree = nullptr;
397397

398+
// Emit poisoning into scratch BB that comes right after prolog.
399+
// We cannot emit this code in the prolog as it might make the prolog too large.
400+
if (compiler->compShouldPoisonFrame() && compiler->fgBBisScratch(block))
401+
{
402+
genPoisonFrame(newLiveRegSet);
403+
}
404+
398405
// Traverse the block in linear order, generating code for each node as we
399406
// as we encounter it.
400407
CLANG_FORMAT_COMMENT_ANCHOR;

src/coreclr/jit/compiler.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9839,6 +9839,16 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
98399839
return (info.compUnmanagedCallCountWithGCTransition > 0);
98409840
}
98419841

9842+
// Returns true if address-exposed user variables should be poisoned with a recognizable value
9843+
bool compShouldPoisonFrame()
9844+
{
9845+
#ifdef FEATURE_ON_STACK_REPLACEMENT
9846+
if (opts.IsOSR())
9847+
return false;
9848+
#endif
9849+
return !info.compInitMem && opts.compDbgCode;
9850+
}
9851+
98429852
#if defined(DEBUG)
98439853

98449854
void compDispLocalVars();

src/coreclr/jit/flowgraph.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2603,8 +2603,8 @@ void Compiler::fgAddInternal()
26032603
noway_assert(!compIsForInlining());
26042604

26052605
// The backend requires a scratch BB into which it can safely insert a P/Invoke method prolog if one is
2606-
// required. Create it here.
2607-
if (compMethodRequiresPInvokeFrame())
2606+
// required. Similarly, we need a scratch BB for poisoning. Create it here.
2607+
if (compMethodRequiresPInvokeFrame() || compShouldPoisonFrame())
26082608
{
26092609
fgEnsureFirstBBisScratch();
26102610
fgFirstBB->bbFlags |= BBF_DONT_REMOVE;

src/coreclr/jit/lsrabuild.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2333,6 +2333,15 @@ void LinearScan::buildIntervals()
23332333
// assert(block->isRunRarely());
23342334
}
23352335

2336+
// For frame poisoning we generate code into scratch BB right after prolog since
2337+
// otherwise the prolog might become too large. In this case we will put the poison immediate
2338+
// into the scratch register, so it will be killed here.
2339+
if (compiler->compShouldPoisonFrame() && compiler->fgFirstBBisScratch() && block == compiler->fgFirstBB)
2340+
{
2341+
addRefsForPhysRegMask(genRegMask(REG_SCRATCH), currentLoc + 1, RefTypeKill, true);
2342+
currentLoc += 2;
2343+
}
2344+
23362345
LIR::Range& blockRange = LIR::AsRange(block);
23372346
for (GenTree* node : blockRange)
23382347
{

src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,6 @@ private ulong Low64
153153
1e80
154154
};
155155

156-
// Used to fill uninitialized stack variables with non-zero pattern in debug builds
157-
[Conditional("DEBUG")]
158-
private static unsafe void DebugPoison<T>(ref T s) where T : unmanaged
159-
{
160-
MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref s, 1)).Fill(0xCD);
161-
}
162-
163156
#region Decimal Math Helpers
164157

165158
private static unsafe uint GetExponent(float f)
@@ -990,7 +983,6 @@ internal static unsafe void DecAddSub(ref DecCalc d1, ref DecCalc d2, bool sign)
990983
// Have to scale by a bunch. Move the number to a buffer where it has room to grow as it's scaled.
991984
//
992985
Unsafe.SkipInit(out Buf24 bufNum);
993-
DebugPoison(ref bufNum);
994986

995987
bufNum.Low64 = low64;
996988
bufNum.Mid64 = tmp64;
@@ -1339,7 +1331,6 @@ internal static unsafe void VarDecMul(ref DecCalc d1, ref DecCalc d2)
13391331
ulong tmp;
13401332
uint hiProd;
13411333
Unsafe.SkipInit(out Buf24 bufProd);
1342-
DebugPoison(ref bufProd);
13431334

13441335
if ((d1.High | d1.Mid) == 0)
13451336
{
@@ -1920,7 +1911,6 @@ internal static int GetHashCode(in decimal d)
19201911
internal static unsafe void VarDecDiv(ref DecCalc d1, ref DecCalc d2)
19211912
{
19221913
Unsafe.SkipInit(out Buf12 bufQuo);
1923-
DebugPoison(ref bufQuo);
19241914

19251915
uint power;
19261916
int curScale;
@@ -2021,7 +2011,6 @@ internal static unsafe void VarDecDiv(ref DecCalc d1, ref DecCalc d2)
20212011
// Shift both dividend and divisor left by curScale.
20222012
//
20232013
Unsafe.SkipInit(out Buf16 bufRem);
2024-
DebugPoison(ref bufRem);
20252014

20262015
bufRem.Low64 = d1.Low64 << curScale;
20272016
bufRem.High64 = (d1.Mid + ((ulong)d1.High << 32)) >> (32 - curScale);
@@ -2090,7 +2079,6 @@ internal static unsafe void VarDecDiv(ref DecCalc d1, ref DecCalc d2)
20902079
// Start by finishing the shift left by curScale.
20912080
//
20922081
Unsafe.SkipInit(out Buf12 bufDivisor);
2093-
DebugPoison(ref bufDivisor);
20942082

20952083
bufDivisor.Low64 = divisor;
20962084
bufDivisor.U2 = (uint)((d2.Mid + ((ulong)d2.High << 32)) >> (32 - curScale));
@@ -2243,7 +2231,6 @@ internal static void VarDecMod(ref DecCalc d1, ref DecCalc d2)
22432231
d1.uflags = d2.uflags;
22442232
// Try to scale up dividend to match divisor.
22452233
Unsafe.SkipInit(out Buf12 bufQuo);
2246-
DebugPoison(ref bufQuo);
22472234

22482235
bufQuo.Low64 = d1.Low64;
22492236
bufQuo.U2 = d1.High;
@@ -2303,7 +2290,6 @@ private static unsafe void VarDecModFull(ref DecCalc d1, ref DecCalc d2, int sca
23032290
int shift = BitOperations.LeadingZeroCount(tmp);
23042291

23052292
Unsafe.SkipInit(out Buf28 b);
2306-
DebugPoison(ref b);
23072293

23082294
b.Buf24.Low64 = d1.Low64 << shift;
23092295
b.Buf24.Mid64 = (d1.Mid + ((ulong)d1.High << 32)) >> (32 - shift);
@@ -2358,7 +2344,6 @@ private static unsafe void VarDecModFull(ref DecCalc d1, ref DecCalc d2, int sca
23582344
else
23592345
{
23602346
Unsafe.SkipInit(out Buf12 bufDivisor);
2361-
DebugPoison(ref bufDivisor);
23622347

23632348
bufDivisor.Low64 = d2.Low64 << shift;
23642349
bufDivisor.U2 = (uint)((d2.Mid + ((ulong)d2.High << 32)) >> (32 - shift));
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
4+
public class Program
5+
{
6+
[SkipLocalsInit]
7+
public static unsafe int Main()
8+
{
9+
bool result = true;
10+
11+
int poisoned;
12+
Unsafe.SkipInit(out poisoned);
13+
result &= VerifyPoison(&poisoned, sizeof(int));
14+
15+
GCRef zeroed;
16+
Unsafe.SkipInit(out zeroed);
17+
result &= VerifyZero(Unsafe.AsPointer(ref zeroed), Unsafe.SizeOf<GCRef>());
18+
19+
WithoutGCRef poisoned2;
20+
Unsafe.SkipInit(out poisoned2);
21+
result &= VerifyPoison(&poisoned2, sizeof(WithoutGCRef));
22+
23+
return result ? 100 : 101;
24+
}
25+
26+
[MethodImpl(MethodImplOptions.NoInlining)]
27+
private static unsafe bool VerifyPoison(void* val, int size)
28+
=> AllEq(new Span<byte>(val, size), 0xCD);
29+
30+
[MethodImpl(MethodImplOptions.NoInlining)]
31+
private static unsafe bool VerifyZero(void* val, int size)
32+
=> AllEq(new Span<byte>(val, size), 0);
33+
34+
private static unsafe bool AllEq(Span<byte> span, byte byteVal)
35+
{
36+
foreach (byte b in span)
37+
{
38+
if (b != byteVal)
39+
return false;
40+
}
41+
42+
return true;
43+
}
44+
45+
private struct GCRef
46+
{
47+
public object ARef;
48+
}
49+
50+
private struct WithoutGCRef
51+
{
52+
public double ADouble;
53+
public int ANumber;
54+
public float AFloat;
55+
}
56+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<DebugType>PdbOnly</DebugType>
5+
<Optimize>False</Optimize>
6+
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
7+
</PropertyGroup>
8+
<ItemGroup>
9+
<Compile Include="$(MSBuildProjectName).cs" />
10+
</ItemGroup>
11+
</Project>

src/tests/issues.targets

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1650,6 +1650,9 @@
16501650
<ExcludeList Include = "$(XunitTestBinBase)tracing/eventsource/eventsourcetrace/eventsourcetrace/**">
16511651
<Issue>needs triage</Issue>
16521652
</ExcludeList>
1653+
<ExcludeList Include = "$(XunitTestBinBase)/JIT/Directed/debugging/poison/**">
1654+
<Issue>Tests coreclr JIT's debug poisoning of address taken variables</Issue>
1655+
</ExcludeList>
16531656
</ItemGroup>
16541657

16551658
<!-- Known failures for mono runtime on *all* architectures/operating systems in interpreter runtime mode -->

0 commit comments

Comments
 (0)