Skip to content

Commit

Permalink
Merge pull request #324 from Washi1337/feature/msf-files
Browse files Browse the repository at this point in the history
Basic read/write support for MSF 7.0 files
  • Loading branch information
Washi1337 authored Jun 16, 2022
2 parents 63e85d6 + 6c0eeb3 commit 3a7db2b
Show file tree
Hide file tree
Showing 20 changed files with 1,337 additions and 0 deletions.
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

0 comments on commit 3a7db2b

Please sign in to comment.