-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[cdac] Implement NibbleMap lookup and tests
The execution manager uses a nibble map to quickly map program counter pointers to the beginnings of the native code for the managed method. Implement the lookup algorithm for a nibble map. Start adding unit tests for the nibble map Also for testing in MockMemorySpace simplify ReaderContext, there's nothing special about the descriptor HeapFragments anymore. We can use a uniform reader.
- Loading branch information
1 parent
32d956b
commit bae8e38
Showing
11 changed files
with
701 additions
and
40 deletions.
There are no files selected for viewing
This file contains 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 |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Contract ExecutionManager | ||
|
||
This contract is for mapping a PC adddress to information about the | ||
managed method corresponding to that address. | ||
|
||
|
||
## APIs of contract | ||
|
||
**TODO** | ||
|
||
## Version 1 | ||
|
||
**TODO** Methods | ||
|
||
### NibbleMap | ||
|
||
Version 1 of this contract depends on a "nibble map" data structure | ||
that allows mapping of a code address in a contiguous subsection of | ||
the address space to the pointer to the start of that a code sequence. | ||
It takes advantage of the fact that the code starts are aligned and | ||
are spaced apart to represent their addresses as a 4-bit nibble value. |
This file contains 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 |
---|---|---|
|
@@ -20,6 +20,7 @@ public enum DataType | |
pointer, | ||
|
||
GCHandle, | ||
CodePointer, | ||
Thread, | ||
ThreadStore, | ||
GCAllocContext, | ||
|
This file contains 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
30 changes: 30 additions & 0 deletions
30
...ged/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/TargetCodePointer.cs
This file contains 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 |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
using System; | ||
|
||
namespace Microsoft.Diagnostics.DataContractReader; | ||
|
||
public readonly struct TargetCodePointer : IEquatable<TargetCodePointer> | ||
{ | ||
public static TargetCodePointer Null = new(0); | ||
public readonly ulong Value; | ||
public TargetCodePointer(ulong value) => Value = value; | ||
|
||
public static implicit operator ulong(TargetCodePointer p) => p.Value; | ||
public static implicit operator TargetCodePointer(ulong v) => new TargetCodePointer(v); | ||
|
||
public static bool operator ==(TargetCodePointer left, TargetCodePointer right) => left.Value == right.Value; | ||
public static bool operator !=(TargetCodePointer left, TargetCodePointer right) => left.Value != right.Value; | ||
|
||
public override bool Equals(object? obj) => obj is TargetCodePointer pointer && Equals(pointer); | ||
public bool Equals(TargetCodePointer other) => Value == other.Value; | ||
|
||
public override int GetHashCode() => Value.GetHashCode(); | ||
|
||
public bool Equals(TargetCodePointer x, TargetCodePointer y) => x.Value == y.Value; | ||
public int GetHashCode(TargetCodePointer obj) => obj.Value.GetHashCode(); | ||
|
||
public TargetPointer AsTargetPointer => new(Value); | ||
|
||
public override string ToString() => $"0x{Value:x}"; | ||
} |
5 changes: 5 additions & 0 deletions
5
...e/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/TargetNUInt.cs
This file contains 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 |
---|---|---|
@@ -1,11 +1,16 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
using System; | ||
using System.Diagnostics; | ||
|
||
namespace Microsoft.Diagnostics.DataContractReader; | ||
|
||
|
||
[DebuggerDisplay("{Hex}")] | ||
public readonly struct TargetNUInt | ||
{ | ||
public readonly ulong Value; | ||
public TargetNUInt(ulong value) => Value = value; | ||
|
||
internal string Hex => $"0x{Value:x}"; | ||
} |
This file contains 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
179 changes: 179 additions & 0 deletions
179
...r/Microsoft.Diagnostics.DataContractReader.Contracts/ExecutionManagerHelpers/NibbleMap.cs
This file contains 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 |
---|---|---|
@@ -0,0 +1,179 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Numerics; | ||
|
||
using MapUnit = uint; | ||
|
||
|
||
namespace Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; | ||
|
||
// Given a contiguous region of memory in which we lay out a collection of non-overlapping code blocks that are | ||
// not too small (so that two adjacent ones aren't too close together) and where the start of each code block is preceeded by a code header aligned on some power of 2, | ||
// we can break up the whole memory space into buckets of a fixed size (32-bytes in the current implementation), where | ||
// each bucket either has a code block header or not. | ||
// Thinking of each code block header address as a hex number, we can view it as: [index, offset, zeros] | ||
// where each index gives us a bucket and the offset gives us the position of the header within the bucket. | ||
// We encode each offset into a 4-bit nibble, reserving the special value 0 to mark the places in the map where a method doesn't start. | ||
// | ||
// To find the start of a method given an address we first convert it into a bucket index (giving the map unit) | ||
// and an offset which we can then turn into the index of the nibble that covers that address. | ||
// If the nibble is non-zero, we have the start of a method and it is near the given address. | ||
// If the nibble is zero, we have to search backward first through the current map unit, and then through previous map | ||
// units until we find a non-zero nibble. | ||
#pragma warning disable SA1121 // Use built in alias | ||
internal sealed class NibbleMap | ||
{ | ||
|
||
public static NibbleMap Create(Target target) | ||
{ | ||
uint codeHeaderSize = (uint)target.PointerSize; | ||
return new NibbleMap(target, codeHeaderSize); | ||
} | ||
|
||
private readonly Target _target; | ||
private readonly uint _codeHeaderSize; | ||
private NibbleMap(Target target, uint codeHeaderSize) | ||
{ | ||
_target = target; | ||
_codeHeaderSize = codeHeaderSize; | ||
} | ||
|
||
// Shift the next nibble into the least significant position. | ||
private static T NextNibble<T>(T n) where T : IBinaryInteger<T> | ||
{ | ||
return n >>> 4; | ||
} | ||
|
||
|
||
private const uint MapUnitBytes = sizeof(MapUnit); // our units are going to be 32-bit integers | ||
private const MapUnit NibbleMask = 0x0F; | ||
private const ulong NibblesPerMapUnit = 2 * MapUnitBytes; // 2 nibbles per byte * N bytes per map unit | ||
|
||
// we will partition the address space into buckets of this many bytes. | ||
// There is at most one code block header per bucket. | ||
// normally we would then need Log2(BytesPerBucket) bits to find the exact start address, | ||
// but because code headers are aligned, we can store the offset in a 4-bit nibble instead and shift appropriately to compute | ||
// the effective address | ||
private const ulong BytesPerBucket = 8 * sizeof(MapUnit); | ||
|
||
|
||
// given the index of a nibble in the map, compute how much we have to shift a MapUnit to put that | ||
// nible in the least significant position. | ||
private static int ComputeNibbleShift(ulong mapIdx) | ||
{ | ||
// the low bits of the nibble index give us how many nibbles we have to shift by. | ||
int nibbleOffsetInMapUnit = (int)(mapIdx & (NibblesPerMapUnit - 1)); | ||
return 28 - (nibbleOffsetInMapUnit * 4); // bit shift - 4 bits per nibble | ||
} | ||
|
||
private static ulong ComputeByteOffset(ulong mapIdx, uint nibble) | ||
{ | ||
return mapIdx * BytesPerBucket + (nibble - 1) * MapUnitBytes; | ||
} | ||
private static TargetPointer GetAbsoluteAddress(TargetPointer baseAddress, ulong mapIdx, uint nibble) | ||
{ | ||
return baseAddress + ComputeByteOffset(mapIdx, nibble); | ||
} | ||
|
||
// Given a relative address, decompose it into | ||
// the bucket index and an offset within the bucket. | ||
private static void DecomposeAddress(TargetNUInt relative, out ulong mapIdx, out uint bucketByteIndex) | ||
{ | ||
mapIdx = relative.Value / BytesPerBucket; | ||
bucketByteIndex = ((uint)(relative.Value & (BytesPerBucket - 1)) / MapUnitBytes) + 1; | ||
System.Diagnostics.Debug.Assert(bucketByteIndex == (bucketByteIndex & NibbleMask)); | ||
} | ||
|
||
private static TargetPointer GetMapUnitAddress(TargetPointer mapStart, ulong mapIdx) | ||
{ | ||
return mapStart + (mapIdx / NibblesPerMapUnit) * MapUnitBytes; | ||
} | ||
|
||
internal static TargetPointer RoundTripAddress(TargetPointer mapBase, TargetPointer currentPC) | ||
{ | ||
TargetNUInt relativeAddress = new TargetNUInt(currentPC.Value - mapBase.Value); | ||
DecomposeAddress(relativeAddress, out ulong mapIdx, out uint bucketByteIndex); | ||
return mapBase + ComputeByteOffset(mapIdx, bucketByteIndex); | ||
} | ||
|
||
internal TargetPointer FindMethodCode(TargetPointer mapBase, TargetPointer mapStart, TargetCodePointer currentPC) | ||
{ | ||
TargetNUInt relativeAddress = new TargetNUInt(currentPC.Value - mapBase.Value); | ||
DecomposeAddress(relativeAddress, out ulong mapIdx, out uint bucketByteIndex); | ||
|
||
MapUnit t = _target.Read<MapUnit>(GetMapUnitAddress(mapStart, mapIdx)); | ||
|
||
// shift the nibble we want to the least significant position | ||
t = t >>> ComputeNibbleShift(mapIdx); | ||
uint nibble = t & NibbleMask; | ||
if (nibble != 0 && nibble <= bucketByteIndex) | ||
{ | ||
return GetAbsoluteAddress(mapBase, mapIdx, nibble); | ||
} | ||
|
||
// search backwards through the current map unit | ||
// we processed the lsb nibble, move to the next one | ||
t = NextNibble(t); | ||
|
||
// if there's any nibble set in the current unit, find it | ||
if (t != 0) | ||
{ | ||
mapIdx--; | ||
nibble = t & NibbleMask; | ||
while (nibble == 0) | ||
{ | ||
t = NextNibble(t); | ||
mapIdx--; | ||
nibble = t & NibbleMask; | ||
} | ||
return GetAbsoluteAddress(mapBase, mapIdx, nibble); | ||
} | ||
|
||
// if we were near the beginning of the address space, there is not enough space for the code header, | ||
// so we can stop | ||
if (mapIdx < NibblesPerMapUnit) | ||
{ | ||
return TargetPointer.Null; | ||
} | ||
|
||
// We're now done with the current map index. | ||
// Align the map index and move to the previous map unit, then move back one nibble. | ||
#pragma warning disable IDE0054 // use compound assignment | ||
mapIdx = mapIdx & (~(NibblesPerMapUnit - 1)); | ||
mapIdx--; | ||
#pragma warning restore IDE0054 // use compound assignment | ||
|
||
// read the map unit containing mapIdx and skip over it if it is all zeros | ||
while (mapIdx >= NibblesPerMapUnit) | ||
{ | ||
t = _target.Read<MapUnit>(GetMapUnitAddress(mapStart, mapIdx)); | ||
if (t != 0) | ||
break; | ||
mapIdx -= NibblesPerMapUnit; | ||
} | ||
|
||
// if we went all the way to the front, we didn't find a code header | ||
if (mapIdx < NibblesPerMapUnit) | ||
{ | ||
return TargetPointer.Null; | ||
} | ||
|
||
// move to the correct nibble in the map unit | ||
while (mapIdx != 0 && (t & NibbleMask) == 0) | ||
{ | ||
t = NextNibble(t); | ||
mapIdx--; | ||
} | ||
|
||
if (mapIdx == 0 && t == 0) | ||
{ | ||
return TargetPointer.Null; | ||
} | ||
|
||
nibble = t & NibbleMask; | ||
return GetAbsoluteAddress(mapBase, mapIdx, nibble); | ||
} | ||
|
||
} | ||
#pragma warning restore SA1121 // Use built in alias |
This file contains 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 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.