Skip to content

Commit 2cf6b0d

Browse files
authored
Fix gc_heap::remove_ro_segment (#63473)
1 parent 65a5d0e commit 2cf6b0d

File tree

3 files changed

+205
-1
lines changed

3 files changed

+205
-1
lines changed

src/coreclr/gc/gc.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9404,7 +9404,7 @@ void gc_heap::remove_ro_segment (heap_segment* seg)
94049404

94059405
enter_spin_lock (&gc_heap::gc_lock);
94069406

9407-
seg_table->remove ((uint8_t*)seg);
9407+
seg_table->remove (heap_segment_mem (seg));
94089408
seg_mapping_table_remove_ro_segment (seg);
94099409

94109410
// Locate segment (and previous segment) in the list.

src/tests/GC/API/Frozen/Frozen.cs

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace HelloFrozenSegment
5+
{
6+
using System;
7+
using System.Reflection;
8+
using System.Runtime.InteropServices;
9+
using System.Runtime.CompilerServices;
10+
11+
struct FrozenSegment
12+
{
13+
IntPtr underlyingSegment;
14+
IntPtr underlyingBuffer;
15+
16+
public FrozenSegment(IntPtr underlyingSegment, IntPtr underlyingBuffer)
17+
{
18+
this.underlyingSegment = underlyingSegment;
19+
this.underlyingBuffer = underlyingBuffer;
20+
}
21+
22+
public void Release()
23+
{
24+
GCHelpers.UnregisterFrozenSegment(this.underlyingSegment);
25+
Marshal.FreeHGlobal(this.underlyingBuffer);
26+
}
27+
}
28+
29+
internal static class GCHelpers
30+
{
31+
private static MethodInfo s_registerFrozenSegmentMethod;
32+
private static MethodInfo s_unregisterFrozenSegmentMethod;
33+
34+
private static MethodInfo RegisterFrozenSegmentMethod
35+
{
36+
get
37+
{
38+
if (s_registerFrozenSegmentMethod == null)
39+
{
40+
s_registerFrozenSegmentMethod = typeof(GC).GetMethod("_RegisterFrozenSegment", BindingFlags.NonPublic|BindingFlags.Static);
41+
}
42+
43+
return s_registerFrozenSegmentMethod;
44+
}
45+
}
46+
47+
private static MethodInfo UnregisterFrozenSegmentMethod
48+
{
49+
get
50+
{
51+
if (s_unregisterFrozenSegmentMethod == null)
52+
{
53+
s_unregisterFrozenSegmentMethod = typeof(GC).GetMethod("_UnregisterFrozenSegment", BindingFlags.NonPublic|BindingFlags.Static);
54+
}
55+
56+
return s_unregisterFrozenSegmentMethod;
57+
}
58+
}
59+
60+
public static IntPtr RegisterFrozenSegment(IntPtr buffer, nint size)
61+
{
62+
return (IntPtr)RegisterFrozenSegmentMethod.Invoke(null, new object[]{buffer, size});
63+
64+
}
65+
66+
public static void UnregisterFrozenSegment(IntPtr segment)
67+
{
68+
UnregisterFrozenSegmentMethod.Invoke(null, new object[]{segment});
69+
}
70+
}
71+
72+
internal unsafe class FrozenSegmentBuilder
73+
{
74+
private IntPtr _buffer;
75+
private IntPtr _allocated;
76+
private IntPtr _limit;
77+
78+
// This only work for BaseSize (i.e. not arrays)
79+
private static unsafe short GetObjectSize(IntPtr methodTable)
80+
{
81+
IntPtr pointerToSize = methodTable + 4;
82+
return *((short*)pointerToSize);
83+
}
84+
85+
public FrozenSegmentBuilder(int capacity)
86+
{
87+
_buffer = Marshal.AllocHGlobal(capacity);
88+
for (int i = 0; i < capacity; i++)
89+
{
90+
*((byte*)(_buffer + i)) = 0;
91+
}
92+
_allocated = _buffer + IntPtr.Size;
93+
_limit = _buffer + capacity;
94+
}
95+
96+
public IntPtr Allocate(IntPtr methodTable)
97+
{
98+
if (_allocated == IntPtr.Zero)
99+
{
100+
throw new Exception("Segment already built");
101+
}
102+
int objectSize = GetObjectSize(methodTable);
103+
if ((_allocated + objectSize).CompareTo(_limit) > 0)
104+
{
105+
throw new Exception("OutOfCapacity");
106+
}
107+
108+
IntPtr* pMethodTable = (IntPtr*)_allocated;
109+
*pMethodTable = methodTable;
110+
IntPtr result = _allocated;
111+
_allocated = _allocated + objectSize;
112+
return result;
113+
}
114+
115+
public FrozenSegment GetSegment()
116+
{
117+
if (_allocated == IntPtr.Zero)
118+
{
119+
throw new Exception("Segment already built");
120+
}
121+
122+
nint size = (nint)(_allocated.ToInt64() - _buffer.ToInt64());
123+
_allocated = IntPtr.Zero;
124+
IntPtr segment = GCHelpers.RegisterFrozenSegment(_buffer, size);
125+
return new FrozenSegment(segment, _buffer);
126+
}
127+
}
128+
129+
internal class Node
130+
{
131+
public Node next;
132+
public int number;
133+
}
134+
135+
internal static class Program
136+
{
137+
private static unsafe IntPtr GetMethodTablePointer(object obj)
138+
{
139+
GCHandle gch = GCHandle.Alloc(obj);
140+
IntPtr pointerToPointerToObject = GCHandle.ToIntPtr(gch);
141+
IntPtr pointerToObject = *((IntPtr*)pointerToPointerToObject);
142+
IntPtr methodTable = *((IntPtr*)pointerToObject);
143+
gch.Free();
144+
return methodTable;
145+
}
146+
147+
private static unsafe int Main(string[] args)
148+
{
149+
Node template = new Node();
150+
IntPtr methodTable = GetMethodTablePointer(template);
151+
152+
FrozenSegmentBuilder frozenSegmentBuilder = new FrozenSegmentBuilder(1000);
153+
IntPtr node1Ptr = frozenSegmentBuilder.Allocate(methodTable);
154+
IntPtr node2Ptr = frozenSegmentBuilder.Allocate(methodTable);
155+
156+
FrozenSegment frozenSegment = frozenSegmentBuilder.GetSegment();
157+
Node root = new Node();
158+
Node node1 = Unsafe.AsRef<Node>((void*)&node1Ptr);
159+
Node node2 = Unsafe.AsRef<Node>((void*)&node2Ptr);
160+
// It is okay for any object to reference a frozen object.
161+
root.next = node1;
162+
163+
// It is not okay for a frozen object to reference another frozen object
164+
// This is because the WriteBarrier code may (depending on the pointer
165+
// value returned by AllocHGlobal) determine node2 to be an ephemeral object
166+
// when it isn't.
167+
// node1.next = node2;
168+
169+
// It is not okay for a frozen object to reference another object that is not frozen
170+
// This is because we may miss the marking of the new Node or miss the relocation
171+
// of the new Node.
172+
// node2.next = new Node();
173+
174+
// Making changes to non-GC references is fine
175+
node1.number = 10086;
176+
node2.number = 12580;
177+
178+
GC.Collect();
179+
node1 = null;
180+
GC.Collect();
181+
node2 = null;
182+
GC.Collect();
183+
Console.WriteLine(root.next.next != null);
184+
frozenSegment.Release();
185+
return 100;
186+
}
187+
}
188+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<CLRTestTargetUnsupported Condition="'$(RuntimeFlavor)' != 'coreclr'">true</CLRTestTargetUnsupported>
5+
<CLRTestPriority>1</CLRTestPriority>
6+
<GCStressIncompatible>true</GCStressIncompatible>
7+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
8+
</PropertyGroup>
9+
<PropertyGroup>
10+
<!-- Set to 'Full' if the Debug? column is marked in the spreadsheet. Leave blank otherwise. -->
11+
<DebugType>PdbOnly</DebugType>
12+
</PropertyGroup>
13+
<ItemGroup>
14+
<Compile Include="Frozen.cs" />
15+
</ItemGroup>
16+
</Project>

0 commit comments

Comments
 (0)