in src/Microsoft.Diagnostics.Runtime/Utilities/PEImage/PEImage.cs [305:511]
private int DoRead(ref int offset, Span<byte> dest)
{
int beginRead = offset;
int endRead = offset + dest.Length - 1;
int beginRelocation = -1;
int endRelocation = -1;
if (_relocations != null)
{
//
// The _relocations array is an array of offsets that begin and end (both inclusively)
// relocations.
//
// For example:
// 32, 35, 36, 39 means [32, 36) and [36, 40) contains data that needs to be relocated.
//
// It is assumed that these never overlaps, so the array contain no duplicates, and it
// is sorted, the array is prepared in the constructor by parsing the .reloc entries.
//
// The even/odd index always correspond to an open/close of the relocation interval.
//
// There is a possibility that the requested range partially contains a relocation, so
// we need to make sure the reading range is extended in those cases. The code below
// uses binary search to find the containing relocation records and deciding on
// exactly how much we wanted to extend the read.
//
// The code also keeps track of which relocation to start and stop to apply so that
// we can apply them after reading.
//
int beginSearch = Array.BinarySearch(_relocations, offset);
if (beginSearch < 0)
{
int beginSearchComplement = ~beginSearch;
if (beginSearchComplement == _relocations.Length)
{
// Case 1: ]
// ^
// The read range starts after all relocations finishes, no need to extend the read
beginRelocation = _relocations.Length;
}
else if ((beginSearchComplement & 1) == 0)
{
// Case 2: ] [
// ^
// The read range starts between relocations, no need to extend the read
beginRelocation = beginSearchComplement;
}
else
{
// Case 3: [ ]
// ^
// The read range starts within a relocation, extend the read
Debug.Assert((beginSearchComplement & 1) == 1);
Debug.Assert(beginSearch > 0);
beginRelocation = beginSearch - 1;
beginRead = _relocations[beginRelocation];
}
}
else
{
if ((beginSearch & 1) == 0)
{
// Case 4: [
// ^
// The read range starts exactly where a relocation start, no need to extend the read
beginRelocation = beginSearch;
}
else
{
// Case 5: ]
// ^
// The read range starts exactly where a relocation end, extend the read
Debug.Assert(beginSearch > 0);
Debug.Assert((beginSearch & 1) == 1);
beginRelocation = beginSearch - 1;
beginRead = _relocations[beginRelocation];
}
}
int endSearch = Array.BinarySearch(_relocations, offset + dest.Length - 1);
if (endSearch < 0)
{
int endSearchComplement = ~endSearch;
if (endSearchComplement == _relocations.Length)
{
// Case 1: ]
// ^
// The read range ends after all relocations finishes, no need to extend the read
endRelocation = _relocations.Length - 1;
}
else if ((endSearchComplement & 1) == 0)
{
// Case 2: ] [
// ^
// The read range ends between relocations, no need to extend the read
endRelocation = endSearchComplement - 1;
}
else
{
// Case 3: [ ]
// ^
// The read range ends within a relocation, extend the read
Debug.Assert((endSearchComplement & 1) == 1);
Debug.Assert(endSearch > 0);
endRelocation = endSearch;
endRead = _relocations[endRelocation];
}
}
else
{
if ((endSearch & 1) == 0)
{
// Case 4: [
// ^
// The read range ends exactly where a relocation start, extend the read
endRelocation = endSearch + 1;
endRead = _relocations[endRelocation];
}
else
{
// Case 5: ]
// ^
// The read range ends exactly where a relocation end, no need to extend the read
Debug.Assert(endSearch > 0);
Debug.Assert((endSearch & 1) == 1);
endRelocation = endSearch;
}
}
Debug.Assert((beginRelocation & 1) == 0);
Debug.Assert((endRelocation & 1) == 1);
Debug.Assert(beginRead <= offset);
Debug.Assert(offset + dest.Length - 1 <= endRead);
}
int readSize = endRead - beginRead + 1;
int headShift = offset - beginRead;
byte[] rawBuffer = ArrayPool<byte>.Shared.Rent(readSize);
Span<byte> buffer = new(rawBuffer, 0, readSize);
SeekTo(beginRead);
int read = _stream.Read(buffer);
if (read < headShift)
{
// This is unexpected, we failed to read something that is supposed to be a reloc.
SeekTo(_offset);
offset = _offset;
return 0;
}
read -= headShift;
if (read > dest.Length)
{
read = dest.Length;
}
SeekTo(offset + read);
offset += read;
_offset = offset;
if ((_relocations != null) && (beginRelocation < endRelocation))
{
for (int r = beginRelocation; r < endRelocation; r += 2)
{
int relocationStartOffset = _relocations[r];
int relocationEndOffset = _relocations[r + 1];
int relocationSize = relocationEndOffset - relocationStartOffset + 1;
Debug.Assert(relocationStartOffset >= beginRead);
Debug.Assert(relocationEndOffset <= endRead);
byte[] beforeBytes = new byte[relocationSize];
for (int i = 0; i < relocationSize; i++)
beforeBytes[i] = buffer[relocationStartOffset - beginRead + i];
byte[]? afterBytes;
if (relocationSize == 4)
{
uint beforeValue = BitConverter.ToUInt32(beforeBytes, 0);
uint afterValue = beforeValue + (uint)(_loadedImageBase - _imageBase);
afterBytes = BitConverter.GetBytes(afterValue);
}
else
{
Debug.Assert(relocationSize == 8);
ulong beforeValue = BitConverter.ToUInt64(beforeBytes, 0);
ulong afterValue = beforeValue - _imageBase + _loadedImageBase;
afterBytes = BitConverter.GetBytes(afterValue);
}
Debug.Assert(afterBytes.Length == relocationSize);
for (int i = 0; i < relocationSize; i++)
{
buffer[relocationStartOffset - beginRead + i] = afterBytes[i];
}
}
}
for (int i = 0; i < read; i++)
{
dest[i] = buffer[i + headShift];
}
ArrayPool<byte>.Shared.Return(rawBuffer);
return read;
}