|
// 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.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Security.Cryptography;
using ILCompiler.DependencyAnalysis.ReadyToRun;
using ILCompiler.DependencyAnalysisFramework;
using ILCompiler.Diagnostics;
using ILCompiler.ObjectWriter;
using ILCompiler.PEWriter;
using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;
using ObjectData = ILCompiler.DependencyAnalysis.ObjectNode.ObjectData;
namespace ILCompiler.DependencyAnalysis
{
/// <summary>
/// Object writer using R2RPEReader to directly emit Windows Portable Executable binaries
/// </summary>
internal class ReadyToRunObjectWriter
{
/// <summary>
/// Nodefactory for which ObjectWriter is instantiated for.
/// </summary>
private readonly NodeFactory _nodeFactory;
/// <summary>
/// Output executable path.
/// </summary>
private readonly string _objectFilePath;
/// <summary>
/// Set to non-null when rewriting MSIL assemblies during composite R2R build;
/// we basically publish the input assemblies into the composite build output folder
/// using the same ReadyToRunObjectWriter as we're using for emitting the "actual"
/// R2R executable, just in this special mode in which we emit a minimal R2R header
/// with forwarding information pointing at the composite module with native code.
/// </summary>
private readonly EcmaModule _componentModule;
/// <summary>
/// Compilation input files. Input files are emitted as perfmap entries and used
/// to calculate the output GUID of the ReadyToRun executable for symbol indexation.
/// </summary>
private readonly IEnumerable<string> _inputFiles;
/// <summary>
/// Nodes to emit into the output executable as collected by the dependency analysis.
/// </summary>
private readonly IReadOnlyCollection<DependencyNode> _nodes;
/// <summary>
/// Set to non-null when generating symbol info or profile info.
/// </summary>
private readonly OutputInfoBuilder _outputInfoBuilder;
/// <summary>
/// Set to non-null when the executable generator should output a map file.
/// </summary>
private readonly MapFileBuilder _mapFileBuilder;
/// <summary>
/// Set to non-null when generating symbol info (PDB / PerfMap).
/// </summary>
private readonly SymbolFileBuilder _symbolFileBuilder;
/// <summary>
/// Set to non-null when generating callchain profile info.
/// </summary>
private readonly ProfileFileBuilder _profileFileBuilder;
/// <summary>
/// True when the map file builder should emit a textual map file
/// </summary>
private bool _generateMapFile;
/// <summary>
/// True when the map file builder should emit a CSV formatted map file
/// </summary>
private bool _generateMapCsvFile;
/// <summary>
/// True when the map file builder should emit a PDB symbol file (only supported on Windows)
/// </summary>
private bool _generatePdbFile;
/// <summary>
/// Explicit specification of the output PDB path
/// </summary>
private string _pdbPath;
/// <summary>
/// True when the map file builder should emit a PerfMap file
/// </summary>
private bool _generatePerfMapFile;
/// <summary>
/// Explicit specification of the output PerfMap path
/// </summary>
private string _perfMapPath;
/// <summary>
/// Requested version of the perfmap file format
/// </summary>
private int _perfMapFormatVersion;
/// <summary>
/// If non-zero, the PE file will be laid out such that it can naturally be mapped with a higher alignment than 4KB.
/// This is used to support loading via large pages on Linux.
/// </summary>
private readonly int _customPESectionAlignment;
public ReadyToRunObjectWriter(
string objectFilePath,
EcmaModule componentModule,
IEnumerable<string> inputFiles,
IReadOnlyCollection<DependencyNode> nodes,
NodeFactory factory,
bool generateMapFile,
bool generateMapCsvFile,
bool generatePdbFile,
string pdbPath,
bool generatePerfMapFile,
string perfMapPath,
int perfMapFormatVersion,
bool generateProfileFile,
CallChainProfile callChainProfile,
int customPESectionAlignment)
{
_objectFilePath = objectFilePath;
_componentModule = componentModule;
_inputFiles = inputFiles;
_nodes = nodes;
_nodeFactory = factory;
_customPESectionAlignment = customPESectionAlignment;
_generateMapFile = generateMapFile;
_generateMapCsvFile = generateMapCsvFile;
_generatePdbFile = generatePdbFile;
_pdbPath = pdbPath;
_generatePerfMapFile = generatePerfMapFile;
_perfMapPath = perfMapPath;
_perfMapFormatVersion = perfMapFormatVersion;
bool generateMap = (generateMapFile || generateMapCsvFile);
bool generateSymbols = (generatePdbFile || generatePerfMapFile);
if (generateMap || generateSymbols || generateProfileFile)
{
_outputInfoBuilder = new OutputInfoBuilder();
if (generateMap)
{
_mapFileBuilder = new MapFileBuilder(_outputInfoBuilder);
}
if (generateSymbols)
{
_symbolFileBuilder = new SymbolFileBuilder(_outputInfoBuilder, _nodeFactory.Target);
}
if (generateProfileFile)
{
_profileFileBuilder = new ProfileFileBuilder(_outputInfoBuilder, callChainProfile, _nodeFactory.Target);
}
}
}
public void EmitReadyToRunObjects(ReadyToRunContainerFormat format, Logger logger)
{
bool succeeded = false;
try
{
var stopwatch = Stopwatch.StartNew();
ObjectWriter.ObjectWriter objectWriter = format switch
{
ReadyToRunContainerFormat.PE => CreatePEObjectWriter(),
ReadyToRunContainerFormat.MachO => CreateMachObjectWriter(),
ReadyToRunContainerFormat.Wasm => CreateWasmObjectWriter(),
_ => throw new UnreachableException()
};
using FileStream stream = new FileStream(_objectFilePath, FileMode.Create);
objectWriter.EmitObject(stream, _nodes, dumper: null, logger);
if (_outputInfoBuilder is not null)
{
foreach (MethodWithGCInfo methodNode in _nodeFactory.EnumerateCompiledMethods())
_outputInfoBuilder.AddMethod(methodNode, methodNode);
}
if (_mapFileBuilder != null)
{
_mapFileBuilder.SetFileSize(stream.Length);
}
if (_outputInfoBuilder is not null)
{
foreach (string inputFile in _inputFiles)
{
_outputInfoBuilder.AddInputModule(_nodeFactory.TypeSystemContext.GetModuleFromPath(inputFile));
}
}
if (_generateMapFile)
{
string mapFileName = Path.ChangeExtension(_objectFilePath, ".map");
_mapFileBuilder.SaveMap(mapFileName);
}
if (_generateMapCsvFile)
{
string nodeStatsCsvFileName = Path.ChangeExtension(_objectFilePath, ".nodestats.csv");
string mapCsvFileName = Path.ChangeExtension(_objectFilePath, ".map.csv");
_mapFileBuilder.SaveCsv(nodeStatsCsvFileName, mapCsvFileName);
}
if (_generatePdbFile)
{
string path = _pdbPath;
if (string.IsNullOrEmpty(path))
{
path = Path.GetDirectoryName(_objectFilePath);
}
_symbolFileBuilder.SavePdb(path, _objectFilePath);
}
if (_generatePerfMapFile)
{
string path = _perfMapPath;
if (string.IsNullOrEmpty(path))
{
path = Path.GetDirectoryName(_objectFilePath);
}
_symbolFileBuilder.SavePerfMap(path, _perfMapFormatVersion, _objectFilePath);
}
if (_profileFileBuilder != null)
{
string path = Path.ChangeExtension(_objectFilePath, ".profile");
_profileFileBuilder.SaveProfile(path);
}
stopwatch.Stop();
if (logger.IsVerbose)
logger.LogMessage($"Done writing object file in {stopwatch.Elapsed}");
succeeded = true;
}
finally
{
if (!succeeded)
{
// If there was an exception while generating the OBJ file, make sure we don't leave the unfinished
// object file around.
try
{
File.Delete(_objectFilePath);
}
catch
{
}
}
}
}
private PEObjectWriter CreatePEObjectWriter()
{
int? timeDateStamp;
if (_nodeFactory.CompilationModuleGroup.IsCompositeBuildMode && _componentModule == null)
{
timeDateStamp = null;
}
else
{
PEReader inputPeReader = (_componentModule != null ? _componentModule.PEReader : _nodeFactory.CompilationModuleGroup.CompilationModuleSet.First().PEReader);
timeDateStamp = inputPeReader.PEHeaders.CoffHeader.TimeDateStamp;
}
PEObjectWriter objectWriter = new(_nodeFactory, ObjectWritingOptions.None, _outputInfoBuilder, _objectFilePath, _customPESectionAlignment, timeDateStamp);
if (_nodeFactory.CompilationModuleGroup.IsCompositeBuildMode && _componentModule == null)
{
string configuredSymbolName = _nodeFactory.CompositeImageSettings?.ReadyToRunHeaderSymbolName;
string symbolName = string.IsNullOrWhiteSpace(configuredSymbolName) ? "RTR_HEADER" : configuredSymbolName;
objectWriter.AddExportedSymbol(symbolName);
}
return objectWriter;
}
private MachObjectWriter CreateMachObjectWriter()
{
return new MachObjectWriter(_nodeFactory, ObjectWritingOptions.None, _outputInfoBuilder, baseSymbolName: "__mh_dylib_header");
}
private WasmObjectWriter CreateWasmObjectWriter()
{
return new WasmObjectWriter(_nodeFactory, ObjectWritingOptions.None, _outputInfoBuilder);
}
public static void EmitObject(
string objectFilePath,
EcmaModule componentModule,
IEnumerable<string> inputFiles,
IReadOnlyCollection<DependencyNode> nodes,
NodeFactory factory,
bool generateMapFile,
bool generateMapCsvFile,
bool generatePdbFile,
string pdbPath,
bool generatePerfMapFile,
string perfMapPath,
int perfMapFormatVersion,
bool generateProfileFile,
CallChainProfile callChainProfile,
ReadyToRunContainerFormat format,
int customPESectionAlignment,
Logger logger)
{
Console.WriteLine($@"Emitting R2R {format} file: {objectFilePath}");
ReadyToRunObjectWriter objectWriter = new ReadyToRunObjectWriter(
objectFilePath,
componentModule,
inputFiles,
nodes,
factory,
generateMapFile: generateMapFile,
generateMapCsvFile: generateMapCsvFile,
generatePdbFile: generatePdbFile,
pdbPath: pdbPath,
generatePerfMapFile: generatePerfMapFile,
perfMapPath: perfMapPath,
perfMapFormatVersion: perfMapFormatVersion,
generateProfileFile: generateProfileFile,
callChainProfile,
customPESectionAlignment);
objectWriter.EmitReadyToRunObjects(format, logger);
}
}
}
|