Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic read/write support for MSF 7.0 files #324

Merged
merged 6 commits into from
Jun 16, 2022
Merged
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
30 changes: 30 additions & 0 deletions AsmResolver.sln
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Build.props = Directory.Build.props
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsmResolver.Symbols.WindowsPdb", "src\AsmResolver.Symbols.WindowsPdb\AsmResolver.Symbols.WindowsPdb.csproj", "{9E311832-D0F2-42CA-84DD-9A91B88F0287}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsmResolver.Symbols.WindowsPdb.Tests", "test\AsmResolver.Symbols.WindowsPdb.Tests\AsmResolver.Symbols.WindowsPdb.Tests.csproj", "{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -423,6 +427,30 @@ Global
{2D1DF5DA-7367-4490-B3F0-B996348E150B}.Release|x64.Build.0 = Release|Any CPU
{2D1DF5DA-7367-4490-B3F0-B996348E150B}.Release|x86.ActiveCfg = Release|Any CPU
{2D1DF5DA-7367-4490-B3F0-B996348E150B}.Release|x86.Build.0 = Release|Any CPU
{9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|x64.ActiveCfg = Debug|Any CPU
{9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|x64.Build.0 = Debug|Any CPU
{9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|x86.ActiveCfg = Debug|Any CPU
{9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|x86.Build.0 = Debug|Any CPU
{9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|Any CPU.Build.0 = Release|Any CPU
{9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|x64.ActiveCfg = Release|Any CPU
{9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|x64.Build.0 = Release|Any CPU
{9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|x86.ActiveCfg = Release|Any CPU
{9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|x86.Build.0 = Release|Any CPU
{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|x64.ActiveCfg = Debug|Any CPU
{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|x64.Build.0 = Debug|Any CPU
{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|x86.ActiveCfg = Debug|Any CPU
{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|x86.Build.0 = Debug|Any CPU
{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|Any CPU.Build.0 = Release|Any CPU
{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x64.ActiveCfg = Release|Any CPU
{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x64.Build.0 = Release|Any CPU
{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x86.ActiveCfg = Release|Any CPU
{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -460,6 +488,8 @@ Global
{40483E28-C703-4933-BA5B-9512EF6E6A21} = {EA971BB0-94BA-44DB-B16C-212D2DB27E17}
{CF6A7E02-37DC-4963-AC14-76D74ADCD87A} = {B3AF102B-ABE1-41B2-AE48-C40702F45AB0}
{2D1DF5DA-7367-4490-B3F0-B996348E150B} = {B3AF102B-ABE1-41B2-AE48-C40702F45AB0}
{9E311832-D0F2-42CA-84DD-9A91B88F0287} = {34A95168-A162-4F6A-803B-B6F221FE9EA6}
{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE} = {786C1732-8C96-45DD-97BB-639C9AA7F45B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3302AC79-6D23-4E7D-8C5F-C0C7261044D0}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Title>AsmResolver</Title>
<Description>Windows PDB models for the AsmResolver executable file inspection toolsuite.</Description>
<PackageTags>windows pdb symbols</PackageTags>
<Nullable>enable</Nullable>
<TargetFrameworks>net6.0;netcoreapp3.1;netstandard2.0</TargetFrameworks>
<IsTrimmable>true</IsTrimmable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DocumentationFile>bin\Debug\netstandard2.0\AsmResolver.Symbols.WindowsPdb.xml</DocumentationFile>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DocumentationFile>bin\Release\netstandard2.0\AsmResolver.xml</DocumentationFile>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\AsmResolver\AsmResolver.csproj" />
</ItemGroup>

</Project>
38 changes: 38 additions & 0 deletions src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/FreeBlockMap.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Collections;
using AsmResolver.IO;

namespace AsmResolver.Symbols.WindowsPdb.Msf.Builder;

/// <summary>
/// Represents a block within a MSF file that contains information on which blocks in the MSF file are free to use.
/// </summary>
public class FreeBlockMap : SegmentBase
{
/// <summary>
/// Creates a new empty free block map.
/// </summary>
/// <param name="blockSize">The size of a single block in the MSF file.</param>
public FreeBlockMap(uint blockSize)
{
BitField = new BitArray((int) blockSize * 8, true);
}

/// <summary>
/// Gets the bit field indicating which blocks in the MSF file are free to use.
/// </summary>
public BitArray BitField
{
get;
}

/// <inheritdoc />
public override uint GetPhysicalSize() => (uint) (BitField.Count / 8);

/// <inheritdoc />
public override void Write(IBinaryStreamWriter writer)
{
byte[] data = new byte[BitField.Count / 8];
BitField.CopyTo(data, 0);
writer.WriteBytes(data);
}
}
14 changes: 14 additions & 0 deletions src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/IMsfFileBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace AsmResolver.Symbols.WindowsPdb.Msf.Builder;

/// <summary>
/// Provides members for constructing new MSF files.
/// </summary>
public interface IMsfFileBuilder
{
/// <summary>
/// Reconstructs a new writable MSF file buffer from an instance of <see cref="MsfFile"/>.
/// </summary>
/// <param name="file">The file to reconstruct.</param>
/// <returns>The reconstructed buffer.</returns>
MsfFileBuffer CreateFile(MsfFile file);
}
205 changes: 205 additions & 0 deletions src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/MsfFileBuffer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
using System;
using System.Collections.Generic;
using System.IO;
using AsmResolver.IO;

namespace AsmResolver.Symbols.WindowsPdb.Msf.Builder;

/// <summary>
/// Represents a mutable buffer for building up a new MSF file.
/// </summary>
public class MsfFileBuffer : SegmentBase
{
private readonly Dictionary<MsfStream, int[]> _blockIndices = new();
private readonly List<FreeBlockMap> _freeBlockMaps = new(2);
private readonly List<ISegment?> _blocks;

/// <summary>
/// Creates a new empty MSF file buffer.
/// </summary>
/// <param name="blockSize">The block size to use.</param>
public MsfFileBuffer(uint blockSize)
{
SuperBlock = new MsfSuperBlock
{
Signature = MsfSuperBlock.BigMsfSignature,
BlockSize = blockSize,
FreeBlockMapIndex = 1,
BlockCount = 3,
};

_blocks = new List<ISegment?>((int) blockSize);

InsertBlock(0, SuperBlock);
var fpm = GetOrCreateFreeBlockMap(1, out _);
InsertBlock(2, null);

fpm.BitField[0] = false;
fpm.BitField[1] = false;
fpm.BitField[2] = false;
}

/// <summary>
/// Gets the super block of the MSF file that is being constructed.
/// </summary>
public MsfSuperBlock SuperBlock
{
get;
}

/// <summary>
/// Determines whether a block in the MSF file buffer is available or not.
/// </summary>
/// <param name="blockIndex">The index of the block.</param>
/// <returns><c>true</c> if the block is available, <c>false</c> otherwise.</returns>
public bool BlockIsAvailable(int blockIndex)
{
var freeBlockMap = GetOrCreateFreeBlockMap(blockIndex, out int offset);
if (offset < 3 && (blockIndex == 0 || offset > 0))
return false;
return freeBlockMap.BitField[offset];
}

/// <summary>
/// Inserts a block of the provided MSF stream into the buffer.
/// </summary>
/// <param name="blockIndex">The MSF file index to insert the block into.</param>
/// <param name="stream">The stream to pull a chunk from.</param>
/// <param name="chunkIndex">The index of the chunk to store at the provided block index.</param>
/// <exception cref="ArgumentException">
/// Occurs when the index provided by <paramref name="blockIndex"/> is already in use.
/// </exception>
public void InsertBlock(int blockIndex, MsfStream stream, int chunkIndex)
{
var fpm = GetOrCreateFreeBlockMap(blockIndex, out int offset);
if (!fpm.BitField[offset])
throw new ArgumentException($"Block {blockIndex} is already in use.");

uint blockSize = SuperBlock.BlockSize;
var segment = new DataSourceSegment(
stream.Contents,
stream.Contents.BaseAddress + (ulong) (chunkIndex * blockSize),
(uint) (chunkIndex * blockSize),
(uint) Math.Min(stream.Contents.Length - (ulong) (chunkIndex * blockSize), blockSize));

InsertBlock(blockIndex, segment);

int[] indices = GetMutableBlockIndicesForStream(stream);
indices[chunkIndex] = blockIndex;

fpm.BitField[offset] = false;
}

private void InsertBlock(int blockIndex, ISegment? segment)
{
// Ensure enough blocks are present in the backing-buffer.
while (_blocks.Count <= blockIndex)
_blocks.Add(null);

// Insert block and update super block.
_blocks[blockIndex] = segment;
SuperBlock.BlockCount = (uint) _blocks.Count;
}

private FreeBlockMap GetOrCreateFreeBlockMap(int blockIndex, out int offset)
{
int index = Math.DivRem(blockIndex, (int) SuperBlock.BlockSize, out offset);
while (_freeBlockMaps.Count <= index)
{
var freeBlockMap = new FreeBlockMap(SuperBlock.BlockSize);
_freeBlockMaps.Add(freeBlockMap);
InsertBlock(index + (int) SuperBlock.FreeBlockMapIndex, freeBlockMap);
}

return _freeBlockMaps[index];
}

private int[] GetMutableBlockIndicesForStream(MsfStream stream)
{
if (!_blockIndices.TryGetValue(stream, out int[]? indices))
{
indices = new int[stream.GetRequiredBlockCount(SuperBlock.BlockSize)];
_blockIndices.Add(stream, indices);
}

return indices;
}

/// <summary>
/// Gets the allocated indices for the provided MSF stream.
/// </summary>
/// <param name="stream">The stream.</param>
/// <returns>The block indices.</returns>
public int[] GetBlockIndicesForStream(MsfStream stream) => (int[]) GetMutableBlockIndicesForStream(stream).Clone();

/// <summary>
/// Constructs a new MSF stream containing the stream directory.
/// </summary>
/// <param name="streams">The files that the directory should list.</param>
/// <returns>The constructed stream.</returns>
/// <remarks>
/// This method does <b>not</b> add the stream to the buffer, nor does it update the super block.
/// </remarks>
public MsfStream CreateStreamDirectory(IList<MsfStream> streams)
{
using var contents = new MemoryStream();
var writer = new BinaryStreamWriter(contents);

// Stream count.
writer.WriteInt32(streams.Count);

// Stream sizes.
for (int i = 0; i < streams.Count; i++)
writer.WriteUInt32((uint) streams[i].Contents.Length);

// Stream indices.
for (int i = 0; i < streams.Count; i++)
{
int[] indices = GetMutableBlockIndicesForStream(streams[i]);
foreach (int index in indices)
writer.WriteInt32(index);
}

return new MsfStream(contents.ToArray());
}

/// <summary>
/// Creates a new MSF stream containing the block indices of the stream directory.
/// </summary>
/// <param name="streamDirectory">The stream directory to store the indices for.</param>
/// <returns>The constructed stream.</returns>
/// <remarks>
/// This method does <b>not</b> add the stream to the buffer, nor does it update the super block.
/// </remarks>
public MsfStream CreateStreamDirectoryMap(MsfStream streamDirectory)
{
using var contents = new MemoryStream();
var writer = new BinaryStreamWriter(contents);

int[] indices = GetMutableBlockIndicesForStream(streamDirectory);
foreach (int index in indices)
writer.WriteInt32(index);

return new MsfStream(contents.ToArray());
}

/// <inheritdoc />
public override uint GetPhysicalSize() => SuperBlock.BlockCount * SuperBlock.BlockSize;

/// <inheritdoc />
public override void Write(IBinaryStreamWriter writer)
{
foreach (var block in _blocks)
{
if (block is null)
{
writer.WriteZeroes((int) SuperBlock.BlockSize);
}
else
{
block.Write(writer);
writer.Align(SuperBlock.BlockSize);
}
}
}
}
Loading