Skip to content
Closed
Show file tree
Hide file tree
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
366 changes: 310 additions & 56 deletions src/tools/ilasm/src/ILAssembler/GrammarVisitor.cs

Large diffs are not rendered by default.

106 changes: 52 additions & 54 deletions src/tools/ilasm/src/ILAssembler/VTableExportPEBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,16 +190,15 @@ protected override BlobBuilder SerializeSection(string name, SectionLocation loc
/// </summary>
private void PatchCorHeaderVTableFixups(BlobBuilder textSection, int _)
{
// The COR header is at offset SizeOfImportAddressTable in the text section
// VTableFixups directory is at offset 52 within the COR header (after CodeManagerTable at 44)
// The COR header is at offset SizeOfImportAddressTable in the text section.
bool is32Bit = Header.Machine == Machine.I386 || Header.Machine == 0;
int sizeOfImportAddressTable = (is32Bit || Header.Machine == 0) ? 8 : 0;
int sizeOfImportAddressTable = is32Bit ? 8 : 0;

// COR header offset in text section
int corHeaderOffset = sizeOfImportAddressTable;

// VTableFixups directory entry is at offset 52 within COR header
const int vtableFixupsOffset = 52;
// VTableFixups follows the 8-byte CodeManagerTable directory at offset 40.
const int vtableFixupsOffset = 48;
int patchOffset = corHeaderOffset + vtableFixupsOffset;

// Find the blob containing this offset and patch it
Expand Down Expand Up @@ -371,8 +370,18 @@ private BlobBuilder SerializeSDataSection(SectionLocation location)

_sdataRva = location.RelativeVirtualAddress;

// Calculate sizes for VTableFixups directory
int vtfDirSize = _vtableFixups.Length * 8; // 8 bytes per IMAGE_COR_VTABLEFIXUP entry
var vtableEntryBuilder =
ImmutableArray.CreateBuilder<VTableFixupSupport.VTableFixupEntry>(_vtableFixups.Length);
foreach (VTableFixupInfo info in _vtableFixups)
{
vtableEntryBuilder.Add(new VTableFixupSupport.VTableFixupEntry(
info.SlotCount,
info.Flags,
info.DataLabel));
}
ImmutableArray<VTableFixupSupport.VTableFixupEntry> vtableEntries =
vtableEntryBuilder.MoveToImmutable();
int vtfDirSize = VTableFixupSupport.CalculateVTableFixupsDirectorySize(vtableEntries);

// Calculate slot data size and build slot offset map
var slotOffsets = new Dictionary<(int EntryIndex, int SlotIndex), int>();
Expand All @@ -391,13 +400,27 @@ private BlobBuilder SerializeSDataSection(SectionLocation location)
slotDataOffset += vtf.SlotCount * slotSize;
}

int slotDataEndOffset = slotDataOffset;
int slotDataEndOffset =
vtfDirSize +
VTableFixupSupport.CalculateVTableSlotDataSize(vtableEntries);

// Calculate export-related sizes
int exportStubsOffset = slotDataEndOffset;
int numExports = _exports.Length;
int exportStubSize = GetExportStubSize();
int exportStubsTotalSize = numExports * exportStubSize;
int baseOrdinal = int.MaxValue;
int maxOrdinal = 0;
foreach (ExportInfo export in _exports)
{
baseOrdinal = Math.Min(baseOrdinal, export.Ordinal);
maxOrdinal = Math.Max(maxOrdinal, export.Ordinal);
}
if (baseOrdinal == int.MaxValue)
{
baseOrdinal = 1;
}
int numFunctions = numExports > 0 ? maxOrdinal - baseOrdinal + 1 : 0;

// Export directory comes after export stubs
int exportDirOffset = Align(exportStubsOffset + exportStubsTotalSize, 4);
Expand All @@ -410,7 +433,7 @@ private BlobBuilder SerializeSDataSection(SectionLocation location)
// - Export names (null-terminated strings)
// - DLL name (null-terminated string)
int exportAddrTableOffset = exportDirOffset + 40;
int exportNamePtrTableOffset = exportAddrTableOffset + numExports * 4;
int exportNamePtrTableOffset = exportAddrTableOffset + numFunctions * 4;
int exportOrdinalTableOffset = exportNamePtrTableOffset + numExports * 4;

// Calculate name table size
Expand All @@ -428,38 +451,26 @@ private BlobBuilder SerializeSDataSection(SectionLocation location)
// Store total size for COR header patching (only vtfixup directory, not stubs/exports)
_sdataSize = vtfDirSize;

// Write VTableFixups directory (array of IMAGE_COR_VTABLEFIXUP structures)
var slotDataRvas = new int[_vtableFixups.Length];
int currentSlotDataOffset = vtfDirSize;
foreach (var vtf in _vtableFixups)
for (int i = 0; i < _vtableFixups.Length; i++)
{
int slotDataRva = location.RelativeVirtualAddress + currentSlotDataOffset;
builder.WriteInt32(slotDataRva); // RVA to slot data
builder.WriteUInt16((ushort)vtf.SlotCount); // Count
builder.WriteUInt16(vtf.Flags); // Type/Flags

VTableFixupInfo vtf = _vtableFixups[i];
slotDataRvas[i] = location.RelativeVirtualAddress + currentSlotDataOffset;
bool is64Bit = (vtf.Flags & VTableFixupSupport.COR_VTABLE_64BIT) != 0;
int slotSize = is64Bit ? 8 : 4;
currentSlotDataOffset += vtf.SlotCount * slotSize;
}

// Write slot data (method tokens that get patched by the runtime)
foreach (var vtf in _vtableFixups)
{
bool is64Bit = (vtf.Flags & VTableFixupSupport.COR_VTABLE_64BIT) != 0;

for (int i = 0; i < vtf.SlotCount; i++)
VTableFixupSupport.WriteVTableFixupsDirectory(builder, vtableEntries, slotDataRvas);
VTableFixupSupport.WriteVTableSlotData(
builder,
vtableEntries,
(entryIndex, slotIndex) =>
{
int token = i < vtf.MethodTokens.Length ? vtf.MethodTokens[i] : 0;
if (is64Bit)
{
builder.WriteInt64(token);
}
else
{
builder.WriteInt32(token);
}
}
}
ImmutableArray<int> tokens = _vtableFixups[entryIndex - 1].MethodTokens;
return slotIndex <= tokens.Length ? tokens[slotIndex - 1] : 0;
});

// Write export stubs if we have exports
if (numExports > 0)
Expand Down Expand Up @@ -491,46 +502,33 @@ private BlobBuilder SerializeSDataSection(SectionLocation location)
_exportDirectoryRva = location.RelativeVirtualAddress + builder.Count;

// Write IMAGE_EXPORT_DIRECTORY
int baseOrdinal = int.MaxValue;
int maxOrdinal = 0;
foreach (var export in _exports)
{
if (export.Ordinal < baseOrdinal) baseOrdinal = export.Ordinal;
if (export.Ordinal > maxOrdinal) maxOrdinal = export.Ordinal;
}
if (baseOrdinal == int.MaxValue) baseOrdinal = 1;
int numFunctions = maxOrdinal - baseOrdinal + 1;

int exportDirStart = builder.Count;

builder.WriteUInt32(0); // Characteristics
builder.WriteUInt32(0); // TimeDateStamp (filled later or 0)
builder.WriteUInt16(0); // MajorVersion
builder.WriteUInt16(0); // MinorVersion
builder.WriteInt32(location.RelativeVirtualAddress + exportDirStart + 40 +
numExports * 4 + numExports * 4 + numExports * 2 + nameTableSize); // Name RVA (DLL name)
builder.WriteInt32(location.RelativeVirtualAddress + dllNameOffset); // Name RVA (DLL name)
builder.WriteInt32(baseOrdinal); // Base
builder.WriteInt32(numExports); // NumberOfFunctions
builder.WriteInt32(numFunctions); // NumberOfFunctions
builder.WriteInt32(numExports); // NumberOfNames
builder.WriteInt32(location.RelativeVirtualAddress + exportDirStart + 40); // AddressOfFunctions
builder.WriteInt32(location.RelativeVirtualAddress + exportDirStart + 40 + numExports * 4); // AddressOfNames
builder.WriteInt32(location.RelativeVirtualAddress + exportDirStart + 40 + numExports * 4 * 2); // AddressOfNameOrdinals
builder.WriteInt32(location.RelativeVirtualAddress + exportDirStart + 40 + numFunctions * 4); // AddressOfNames
builder.WriteInt32(location.RelativeVirtualAddress + exportDirStart + 40 + numFunctions * 4 + numExports * 4); // AddressOfNameOrdinals

// Sort exports by name for binary search
var sortedExports = _exports.AsSpan().ToArray();
Array.Sort(sortedExports, (a, b) => string.CompareOrdinal(a.Name, b.Name));

// Write Export Address Table (RVAs to stubs)
var exportsArray = _exports.AsSpan().ToArray();
foreach (var export in sortedExports)
for (int ordinal = baseOrdinal; ordinal <= maxOrdinal; ordinal++)
{
int stubIndex = Array.FindIndex(exportsArray, e => e.Ordinal == export.Ordinal);
builder.WriteInt32(exportStubRvas[stubIndex]);
int stubIndex = Array.FindIndex(exportsArray, export => export.Ordinal == ordinal);
builder.WriteInt32(stubIndex >= 0 ? exportStubRvas[stubIndex] : 0);
}

// Write Export Name Pointer Table (RVAs to names)
int nameOffset = location.RelativeVirtualAddress + exportDirStart + 40 +
numExports * 4 + numExports * 4 + numExports * 2;
int nameOffset = location.RelativeVirtualAddress + exportNamesOffset;
foreach (var export in sortedExports)
{
builder.WriteInt32(nameOffset);
Expand Down
85 changes: 85 additions & 0 deletions src/tools/ilasm/tests/ILAssembler.Tests/AssemblyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -634,5 +634,90 @@ leave.s DONE
var catchRegion = ehRegions.First(r => r.Kind == ExceptionRegionKind.Catch);
Assert.Equal(HandleKind.TypeDefinition, catchRegion.CatchType.Kind);
}

[Fact]
public void AssemblyReferenceMetadata_EmitsExpectedIdentity()
{
string source = """
.assembly extern Sample.Dependency
{
.ver 2:5:0:7
.locale "en-US"
.publickeytoken = (01 23 45 67 89 AB CD EF)
.hash = (AA BB CC)
}
.assembly TestAssembly { }
.class public auto ansi beforefieldinit Test
{
}
""";

using var pe = DocumentCompilerTestHelpers.CompileAndGetReader(source, new Options());
var reader = pe.GetMetadataReader();

var assemblyRef = reader.GetAssemblyReference(MetadataTokens.AssemblyReferenceHandle(1));
Assert.Equal("Sample.Dependency", reader.GetString(assemblyRef.Name));
Assert.Equal(new Version(2, 5, 0, 7), assemblyRef.Version);
Assert.Equal("en-US", reader.GetString(assemblyRef.Culture));
Assert.Equal([0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF], reader.GetBlobBytes(assemblyRef.PublicKeyOrToken));
Assert.Equal([0xAA, 0xBB, 0xCC], reader.GetBlobBytes(assemblyRef.HashValue));
}

[Fact]
public void AssemblyAndAliasedReferenceDeclarations_EmitKeysWildcardsLocalesAndAttributes()
{
string source = """
.assembly extern mscorlib { }
.assembly extern Original.Dependency as Alias
{
.publicKey = (01 02 03 04)
.ver *:2:*:4
.locale = (65 00 6E 00)
.hash = (AA BB)
auto
.custom instance void [mscorlib]System.ObsoleteAttribute::.ctor() = (01 00 00 00)
;
}
.assembly test
{
.publicKey = (10 20 30 40)
.ver *:5:*:7
.locale = (66 00 72 00)
.custom instance void [mscorlib]System.CLSCompliantAttribute::.ctor(bool) = (01 00 01 00 00)
;
}
.class public auto ansi Test
{
.field public class [Alias]Contoso.External Value
}
""";

using var pe = DocumentCompilerTestHelpers.CompileAndGetReader(source, new Options());
var reader = pe.GetMetadataReader();
var assembly = reader.GetAssemblyDefinition();
var dependencyHandle = reader.AssemblyReferences
.Single(handle => reader.GetString(reader.GetAssemblyReference(handle).Name) == "Original.Dependency");
var dependency = reader.GetAssemblyReference(dependencyHandle);
var externalType = reader.TypeReferences
.Select(reader.GetTypeReference)
.Single(type => reader.GetString(type.Name) == "External");

Assert.Equal(new Version(0, 5, 0, 7), assembly.Version);
Assert.Equal("fr", reader.GetString(assembly.Culture));
Assert.True(assembly.Flags.HasFlag(AssemblyFlags.PublicKey));
Assert.Equal([0x10, 0x20, 0x30, 0x40], reader.GetBlobBytes(assembly.PublicKey));
CustomAttributeValue<string> assemblyAttribute = reader
.GetCustomAttribute(Assert.Single(reader.GetAssemblyDefinition().GetCustomAttributes()))
.DecodeValue(DocumentCompilerTestHelpers.Decoder);
Assert.Equal(true, Assert.Single(assemblyAttribute.FixedArguments).Value);

Assert.Equal(new Version(0, 2, 0, 4), dependency.Version);
Assert.Equal("en", reader.GetString(dependency.Culture));
Assert.True(dependency.Flags.HasFlag(AssemblyFlags.PublicKey));
Assert.Equal([0x01, 0x02, 0x03, 0x04], reader.GetBlobBytes(dependency.PublicKeyOrToken));
Assert.Equal([0xAA, 0xBB], reader.GetBlobBytes(dependency.HashValue));
Assert.Single(reader.GetCustomAttributes(dependencyHandle));
Assert.Equal(dependencyHandle, externalType.ResolutionScope);
}
}
}
Loading
Loading