Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ internal sealed partial class MachObjectWriter : UnixObjectWriter
private readonly uint _cpuType;
private readonly uint _cpuSubType;
private readonly List<MachSection> _sections = new();
private readonly List<SymbolDefinition> _temporaryLabels = new();
private uint _temporaryLabelsBaseIndex;

// Symbol table
private readonly Dictionary<Utf8String, uint> _symbolNameToIndex = new();
Expand Down Expand Up @@ -482,10 +484,35 @@ protected internal override unsafe void EmitRelocation(
// subtraction and the PC offset is baked into the addend.
// On x64, ld64 requires X86_64_RELOC_SUBTRACTOR + X86_64_RELOC_UNSIGNED
// for DWARF .eh_frame section.

// Apple ld-prime linker needs a quirk where we restrict the addend to
// signed 20-bit integer. We generate a temporary label every time the
// addend would overflow and adjust the addend to be relative to that label.
const int signedAddendBitWidth = 20;
const int signedAddendShift = (sizeof(long) * 8) - signedAddendBitWidth;
var temporaryLabelOffset = _sections[sectionIndex].TemporaryLabelOffset;
var temporaryLabelIndex = _sections[sectionIndex].TemporaryLabelIndex;
addend -= offset - temporaryLabelOffset;
if (addend != ((addend << signedAddendShift) >> signedAddendShift))
{
addend += offset - temporaryLabelOffset;
_temporaryLabels.Add(new SymbolDefinition(sectionIndex, offset, 0, false));
_sections[sectionIndex].TemporaryLabelOffset = temporaryLabelOffset = offset;
_sections[sectionIndex].TemporaryLabelIndex = temporaryLabelIndex = (uint)_temporaryLabels.Count;
}
Comment thread
filipnavara marked this conversation as resolved.

Debug.Assert(addend >= int.MinValue && addend <= int.MaxValue);
BinaryPrimitives.WriteInt32LittleEndian(
data,
BinaryPrimitives.ReadInt32LittleEndian(data) +
(int)(addend - offset));
(int)addend);
Comment thread
filipnavara marked this conversation as resolved.

// The SymbolicRelocation.Addend field is repurposed to carry the
// 1-based temporary label index (0 means no temporary label). This
// avoids introducing a side table and is consumed by
// EmitRelocationsX64/EmitRelocationsArm64 when building the
// subtractor relocation pair.
addend = temporaryLabelIndex;
Comment on lines +510 to +515
Comment on lines +510 to +515
}
else
{
Expand All @@ -497,8 +524,8 @@ protected internal override unsafe void EmitRelocation(
BinaryPrimitives.ReadInt32LittleEndian(data) +
(int)addend);
}
addend = 0;
}
addend = 0;
break;

default:
Expand All @@ -524,6 +551,23 @@ private protected override void EmitSymbolTable(
// We already emitted symbols for all non-debug sections in EmitSectionsAndLayout,
// these symbols are local and we need to account for them.
uint symbolIndex = (uint)_symbolTable.Count;
_temporaryLabelsBaseIndex = symbolIndex;
Utf8StringBuilder temporaryLabelNameBuilder = new Utf8StringBuilder();
foreach (SymbolDefinition definition in _temporaryLabels)
{
temporaryLabelNameBuilder.Clear();
temporaryLabelNameBuilder.Append("ltemp"u8).Append((int)(symbolIndex - _temporaryLabelsBaseIndex));
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Utf8StringBuilder doesn't have an overload that takes unsigned integers. This should not overflow for any reasonably sized object file.

MachSection section = _sections[definition.SectionIndex];
_symbolTable.Add(new MachSymbol
{
Name = temporaryLabelNameBuilder.ToUtf8String(),
Section = section,
Value = section.VirtualAddress + (ulong)definition.Value,
Descriptor = N_NO_DEAD_STRIP,
Type = N_SECT,
});
symbolIndex++;
}
_dySymbolTable.LocalSymbolsIndex = 0;
_dySymbolTable.LocalSymbolsCount = symbolIndex;

Expand Down Expand Up @@ -658,11 +702,20 @@ private void EmitRelocationsX64(int sectionIndex, List<SymbolicRelocation> reloc
}
else if (symbolicRelocation.Type == IMAGE_REL_BASED_RELPTR32 && IsEhFrameSection(sectionIndex))
{
// Addend is used to encode the temporary label index for ld-prime quirk. See
// EmitRelocation for details.
Debug.Assert(symbolicRelocation.Addend == 0 || symbolicRelocation.Addend <= _temporaryLabels.Count);
uint baseSymbolIndex =
symbolicRelocation.Addend != 0 ?
(uint)(_temporaryLabelsBaseIndex + symbolicRelocation.Addend - 1) :
(uint)sectionIndex;
Debug.Assert(baseSymbolIndex < (uint)_symbolTable.Count);

sectionRelocations.Add(
new MachRelocation
{
Address = (int)symbolicRelocation.Offset,
SymbolOrSectionIndex = (uint)sectionIndex,
SymbolOrSectionIndex = baseSymbolIndex,
Comment thread
filipnavara marked this conversation as resolved.
Length = 4,
RelocationType = X86_64_RELOC_SUBTRACTOR,
IsExternal = true,
Expand Down Expand Up @@ -821,12 +874,21 @@ private void EmitRelocationsArm64(int sectionIndex, List<SymbolicRelocation> rel
}
else if (symbolicRelocation.Type == IMAGE_REL_BASED_RELPTR32)
{
// Addend is used to encode the temporary label index for ld-prime quirk. See
// EmitRelocation for details.
Debug.Assert(symbolicRelocation.Addend == 0 || symbolicRelocation.Addend <= _temporaryLabels.Count);
uint baseSymbolIndex =
symbolicRelocation.Addend != 0 ?
(uint)(_temporaryLabelsBaseIndex + symbolicRelocation.Addend - 1) :
(uint)sectionIndex;
Debug.Assert(baseSymbolIndex < _symbolTable.Count);

// This one is tough... needs to be represented by ARM64_RELOC_SUBTRACTOR + ARM64_RELOC_UNSIGNED.
sectionRelocations.Add(
new MachRelocation
{
Address = (int)symbolicRelocation.Offset,
SymbolOrSectionIndex = (uint)sectionIndex,
SymbolOrSectionIndex = baseSymbolIndex,
Length = 4,
RelocationType = ARM64_RELOC_SUBTRACTOR,
IsExternal = true,
Expand Down Expand Up @@ -975,6 +1037,13 @@ private sealed class MachSection
public Stream Stream => dataStream;
public byte SectionIndex { get; set; }

// Variables used for tracking a temporary label created within the section
// for the purpose of creating relocations with smaller addends. The emitted
// addends are relative to the last temporary label instead of the section
// start.
public long TemporaryLabelOffset { get; set; }
public uint TemporaryLabelIndex { get; set; }

public static int HeaderSize => 80; // 64-bit section

public MachSection(string segmentName, string sectionName, Stream stream)
Expand Down
Loading