230 lines
7.7 KiB
C#
Raw Normal View History

2021-09-20 18:20:01 +02:00
/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Diagnostics;
using dnlib.DotNet.MD;
namespace dnSpy.AsmEditor.Compiler.MDEditor {
sealed class TablesHeapWriter : MDHeapWriter {
public override string Name => tablesHeap.Name;
readonly TablesMDHeap tablesHeap;
readonly StringsMDHeap stringsHeap;
readonly GuidMDHeap guidHeap;
readonly BlobMDHeap blobHeap;
readonly MDStreamFlags mdStreamFlags;
public TablesHeapWriter(TablesMDHeap tablesHeap, StringsMDHeap stringsHeap, GuidMDHeap guidHeap, BlobMDHeap blobHeap) {
this.tablesHeap = tablesHeap;
this.stringsHeap = stringsHeap;
this.guidHeap = guidHeap;
this.blobHeap = blobHeap;
if (stringsHeap.IsBig)
mdStreamFlags |= MDStreamFlags.BigStrings;
if (guidHeap.IsBig)
mdStreamFlags |= MDStreamFlags.BigGUID;
if (blobHeap.IsBig)
mdStreamFlags |= MDStreamFlags.BigBlob;
}
static TablesHeapWriter() {
tablesToIgnore = new bool[0x40];
// Don't include tables the compiler won't need
// Not likely to be used by the compiler
tablesToIgnore[(int)Table.FieldMarshal] = true;
// Used only by method bodies, and we don't write any
tablesToIgnore[(int)Table.StandAloneSig] = true;
// Not likely to be used by the compiler, and we don't write the field data to the new file
tablesToIgnore[(int)Table.FieldRVA] = true;
// Not used by the compiler
tablesToIgnore[(int)Table.ENCLog] = true;
// Not used by the compiler
tablesToIgnore[(int)Table.ENCMap] = true;
// Not used by the compiler
tablesToIgnore[(int)Table.AssemblyProcessor] = true;
// Not used by the compiler
tablesToIgnore[(int)Table.AssemblyOS] = true;
// Not used by the compiler
tablesToIgnore[(int)Table.AssemblyRefProcessor] = true;
// Not used by the compiler
tablesToIgnore[(int)Table.AssemblyRefOS] = true;
// Not likely to be used by the compiler
tablesToIgnore[(int)Table.ManifestResource] = true;
// All portable PDB tables (everything >= 0x30)
tablesToIgnore[(int)Table.Document] = true;
tablesToIgnore[(int)Table.MethodDebugInformation] = true;
tablesToIgnore[(int)Table.LocalScope] = true;
tablesToIgnore[(int)Table.LocalVariable] = true;
tablesToIgnore[(int)Table.LocalConstant] = true;
tablesToIgnore[(int)Table.ImportScope] = true;
tablesToIgnore[(int)Table.StateMachineMethod] = true;
tablesToIgnore[(int)Table.CustomDebugInformation] = true;
tablesToIgnore[0x38] = true;
tablesToIgnore[0x39] = true;
tablesToIgnore[0x3A] = true;
tablesToIgnore[0x3B] = true;
tablesToIgnore[0x3C] = true;
tablesToIgnore[0x3D] = true;
tablesToIgnore[0x3E] = true;
tablesToIgnore[0x3F] = true;
}
static readonly bool[] tablesToIgnore;
public unsafe override void Write(MDWriter mdWriter, MDWriterStream stream, byte[] tempBuffer) {
var tblStream = mdWriter.MetadataEditor.RealMetadata.TablesStream;
stream.Write(tblStream.Reserved1);
stream.Write((byte)(tblStream.Version >> 8));
stream.Write((byte)tblStream.Version);
stream.Write((byte)mdStreamFlags);
stream.Write((byte)1);
stream.Write(GetValidMask(tablesHeap));
stream.Write(GetSortedMask(tablesHeap, tblStream.SortedMask));
var rowCounts = new uint[tablesHeap.TableInfos.Length];
var infos = tablesHeap.TableInfos;
for (int i = 0; i < infos.Length; i++) {
if (tablesToIgnore[i])
continue;
var info = infos[i];
if (info is not null && !info.IsEmpty) {
rowCounts[i] = info.Rows;
stream.Write(info.Rows);
}
}
var dnTableSizes = new DotNetTableSizes();
var tableInfos = dnTableSizes.CreateTables((byte)(tblStream.Version >> 8), (byte)tblStream.Version);
dnTableSizes.InitializeSizes((mdStreamFlags & MDStreamFlags.BigStrings) != 0,
(mdStreamFlags & MDStreamFlags.BigGUID) != 0,
(mdStreamFlags & MDStreamFlags.BigBlob) != 0,
rowCounts, rowCounts);
long totalSize = 0;
for (int i = 0; i < infos.Length; i++) {
if (tablesToIgnore[i])
continue;
var info = infos[i];
if (info is not null && !info.IsEmpty)
totalSize += (long)info.Rows * tableInfos[i].RowSize;
}
// NOTE: We don't write method bodies or field data, the compiler shouldn't
// read Method.RVA. We also don't write the FieldRVA table.
// PERF: Write to a temp buffer followed by a call to stream.Write(byte[]). It's faster
// than calling stream.Write() for every row + column.
var tablesPos = stream.Position;
int tempBufferIndex = 0;
for (int i = 0; i < infos.Length; i++) {
if (tablesToIgnore[i])
continue;
var info = infos[i];
if (info is null || info.IsEmpty)
continue;
var tableWriter = TableWriter.Create(info);
var mdTable = info.MDTable;
var tbl = tableInfos[i];
var columns = tbl.Columns;
var rows = tableWriter.Rows;
uint currentRowIndex = 0;
var rowSize = (uint)tbl.RowSize;
Debug.Assert(tempBuffer.Length >= rowSize, "Temp buffer is too small");
// If there are no changes in the original metadata or layout, just copy everything
uint unmodifiedRows = tableWriter.FirstModifiedRowId - 1;
if (unmodifiedRows > 0 && Equals(mdTable.TableInfo, tbl)) {
if (tempBufferIndex > 0) {
stream.Write(tempBuffer, 0, tempBufferIndex);
tempBufferIndex = 0;
}
stream.Write((byte*)mdWriter.ModuleData.Pointer + (int)mdTable.StartOffset, (int)(unmodifiedRows * mdTable.RowSize));
Debug.Assert(unmodifiedRows <= rows);
rows -= unmodifiedRows;
currentRowIndex += unmodifiedRows;
}
while (rows > 0) {
int bytesLeft = tempBuffer.Length - tempBufferIndex;
uint maxRows = Math.Min((uint)bytesLeft / rowSize, rows);
if (maxRows == 0) {
stream.Write(tempBuffer, 0, tempBufferIndex);
tempBufferIndex = 0;
bytesLeft = tempBuffer.Length;
maxRows = Math.Min((uint)bytesLeft / rowSize, rows);
}
Debug.Assert(maxRows > 0);
for (uint endRowIndex = currentRowIndex + maxRows; currentRowIndex < endRowIndex; currentRowIndex++, tempBufferIndex += (int)rowSize)
tableWriter.WriteRow(currentRowIndex, columns, tempBuffer, tempBufferIndex);
rows -= maxRows;
}
}
if (tempBufferIndex > 0)
stream.Write(tempBuffer, 0, tempBufferIndex);
if (tablesPos + totalSize != stream.Position)
throw new InvalidOperationException();
}
static bool Equals(TableInfo a, TableInfo b) {
Debug.Assert(a.Name == b.Name);
if (a.RowSize != b.RowSize)
return false;
var ac = a.Columns;
var bc = b.Columns;
Debug.Assert(ac.Length == bc.Length);
for (int i = 0; i < ac.Length; i++) {
if (ac[i].Offset != bc[i].Offset)
return false;
}
return true;
}
static ulong GetValidMask(TablesMDHeap tablesHeap) {
ulong mask = 0;
var infos = tablesHeap.TableInfos;
for (int i = 0; i < infos.Length; i++) {
if (tablesToIgnore[i])
continue;
var info = infos[i];
if (info is not null && !info.IsEmpty)
mask |= 1UL << i;
}
return mask;
}
static ulong GetSortedMask(TablesMDHeap tablesHeap, ulong mask) {
var ignore = tablesToIgnore;
for (int i = 0; i < ignore.Length; i++) {
if (ignore[i])
mask &= ~(1UL << i);
}
return mask;
}
}
}