Skip to content

Commit

Permalink
Implement FileVersionInfo for PEImage
Browse files Browse the repository at this point in the history
Addresses microsoft#398 in 1.1
  • Loading branch information
arvindshmicrosoft committed Dec 12, 2019
1 parent 32643cc commit b5fcdb7
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.Diagnostics.Runtime.Utilities;
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Xunit;
Expand All @@ -8,6 +9,23 @@ namespace Microsoft.Diagnostics.Runtime.Tests
{
public class PEImageResourceTests
{
[Fact]
public void FileInfoVersionTest()
{
using DataTarget dt = TestTargets.AppDomains.LoadFullDump();
ModuleInfo clrModule = dt.EnumerateModules().SingleOrDefault(m => Path.GetFileNameWithoutExtension(m.FileName).Equals("clr", StringComparison.OrdinalIgnoreCase));

PEImage img = clrModule.GetPEImage();
Assert.NotNull(img);

FileVersionInfo fileVersion = img.GetFileVersionInfo();
Assert.NotNull(fileVersion);
Assert.NotNull(fileVersion.FileVersion);

ClrInfo clrInfo = dt.ClrVersions[0];
Assert.Contains(clrInfo.Version.ToString(), fileVersion.FileVersion);
}

[Fact]
public void TestResourceImages()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,10 @@
<PackagePath></PackagePath>
</Content>
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Buffers" Version="4.5.0" />
<PackageReference Include="System.Memory" Version="4.5.3" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.6.0" />
</ItemGroup>
</Project>
38 changes: 38 additions & 0 deletions src/Microsoft.Diagnostics.Runtime/src/Extensions/DebugOnly.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Diagnostics;

namespace Microsoft.Diagnostics.Runtime
{
internal static class DebugOnly
{
[Conditional("DEBUG")]
public static void Assert(bool mustBeTrue) => Assert(mustBeTrue, "Assertion Failed");

[Conditional("DEBUG")]
public static void Assert(bool mustBeTrue, string msg)
{
if (!mustBeTrue)
Fail(msg);
}

[Conditional("DEBUG")]
public static void Fail(string message)
{
throw new AssertionException(message);
}
}

#pragma warning disable CA1032 // Implement standard exception constructors
#pragma warning disable CA1064 // Exceptions should be public
internal sealed class AssertionException : Exception
{
public AssertionException(string msg)
: base(msg)
{
}
}
}
58 changes: 58 additions & 0 deletions src/Microsoft.Diagnostics.Runtime/src/Extensions/SpanExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Buffers;
using System.IO;
using System.Runtime.InteropServices;

namespace Microsoft.Diagnostics.Runtime
{
internal static class SpanExtensions
{
public static int Read(this Stream stream, Span<byte> span)
{
byte[] buffer = ArrayPool<byte>.Shared.Rent(span.Length);
try
{
int numRead = stream.Read(buffer, 0, span.Length);
new ReadOnlySpan<byte>(buffer, 0, numRead).CopyTo(span);
return numRead;
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}

public static ulong AsPointer(this Span<byte> span, int index = 0)
{
if (index != 0)
span = span.Slice(index * IntPtr.Size, IntPtr.Size);

DebugOnly.Assert(span.Length >= IntPtr.Size);
return IntPtr.Size == 4
? MemoryMarshal.Read<uint>(span)
: MemoryMarshal.Read<ulong>(span);
}

public static int AsInt32(this Span<byte> span)
{
DebugOnly.Assert(span.Length >= sizeof(int));
return MemoryMarshal.Read<int>(span);
}

public static uint AsUInt32(this Span<byte> span)
{
DebugOnly.Assert(span.Length >= sizeof(uint));
return MemoryMarshal.Read<uint>(span);
}

public static ulong AsUInt64(this Span<byte> span)
{
DebugOnly.Assert(span.Length >= sizeof(ulong));
return MemoryMarshal.Read<ulong>(span);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public FileVersionInfo GetFileVersionInfo()

PEBuffer buff = AllocBuff();
byte* bytes = versionNode.FetchData(0, versionNode.DataLength, buff);
FileVersionInfo ret = new FileVersionInfo(bytes, versionNode.DataLength);
FileVersionInfo ret = new FileVersionInfo(new Span<byte>(bytes, versionNode.DataLength));

FreeBuff(buff);
return ret;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,19 @@ public FileVersionInfo GetFileVersionInfo()
{
PEBuffer buff = _file.AllocBuff();
byte* bytes = FetchData(0, DataLength, buff);
FileVersionInfo ret = new FileVersionInfo(bytes, DataLength);

FileVersionInfo ret = new FileVersionInfo(new Span<byte>(bytes, DataLength));
_file.FreeBuff(buff);
return ret;
}

public override string ToString()
{
StringWriter sw = new StringWriter();
ToString(sw, "");
return sw.ToString();
using (var sw = new StringWriter())
{
ToString(sw, "");
return sw.ToString();
}
}

public static ResourceNode GetChild(ResourceNode node, string name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,50 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;

namespace Microsoft.Diagnostics.Runtime.Utilities
{
/// <summary>
/// FileVersionInfo reprents the extended version formation that is optionally placed in the PE file resource area.
/// </summary>
public sealed unsafe class FileVersionInfo
{
// TODO incomplete, but this is all I need.
/// <summary>
/// Gets the position of the string data within the resource block.
/// See http://msdn.microsoft.com/en-us/library/ms647001(v=VS.85).aspx
/// </summary>
public const int DataOffset = 0x5c;

/// <summary>
/// The verison string
/// </summary>
public string FileVersion { get; }
public string? FileVersion { get; }

/// <summary>
/// Comments to supplement the file version
/// </summary>
public string Comments { get; }
public string? Comments { get; }

internal FileVersionInfo(byte* data, int dataLen)
internal FileVersionInfo(Span<byte> data)
{
FileVersion = "";
if (dataLen <= 0x5c)
return;

// See http://msdn.microsoft.com/en-us/library/ms647001(v=VS.85).aspx
byte* stringInfoPtr = data + 0x5c; // Gets to first StringInfo

// TODO search for FileVersion string ...
string dataAsString = new string((char*)stringInfoPtr, 0, (dataLen - 0x5c) / 2);
data = data.Slice(DataOffset);
fixed (byte* ptr = data)
{
string dataAsString = new string((char*)ptr, 0, data.Length / 2);

FileVersion = GetDataString(dataAsString, "FileVersion");
Comments = GetDataString(dataAsString, "Comments");
FileVersion = GetDataString(dataAsString, "FileVersion");
Comments = GetDataString(dataAsString, "Comments");
}
}

private static string GetDataString(string dataAsString, string fileVersionKey)
private static string? GetDataString(string dataAsString, string fileVersionKey)
{
int fileVersionIdx = dataAsString.IndexOf(fileVersionKey);
if (fileVersionIdx >= 0)
{
int valIdx = fileVersionIdx + fileVersionKey.Length;
for (;;)
for (; ; )
{
valIdx++;
if (valIdx >= dataAsString.Length)
Expand All @@ -61,5 +64,7 @@ private static string GetDataString(string dataAsString, string fileVersionKey)

return null;
}

public override string? ToString() => FileVersion;
}
}
49 changes: 49 additions & 0 deletions src/Microsoft.Diagnostics.Runtime/src/Utilities/PEImage/PEImage.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.Diagnostics.Runtime.Interop;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
Expand Down Expand Up @@ -219,6 +220,54 @@ public int Read(byte[] dest, int virtualAddress, int bytesRequested)
return read;
}

/// <summary>
/// Reads data out of PE image into a native buffer.
/// </summary>
/// <param name="virtualAddress">The address to read from.</param>
/// <param name="dest">The location to write the data.</param>
/// <returns>The number of bytes actually read from the image and written to dest.</returns>
public int Read(int virtualAddress, Span<byte> dest)
{
int offset = RvaToOffset(virtualAddress);
if (offset == -1)
return 0;

SeekTo(offset);
return Stream.Read(dest);
}

/// <summary>
/// Gets the File Version Information that is stored as a resource in the PE file. (This is what the
/// version tab a file's property page is populated with).
/// </summary>
public FileVersionInfo? GetFileVersionInfo()
{
ResourceEntry? versionNode = Resources.Children.FirstOrDefault(r => r.Name == "Version");
if (versionNode == null || versionNode.Children.Count != 1)
return null;

versionNode = versionNode.Children[0];
if (!versionNode.IsLeaf && versionNode.Children.Count == 1)
versionNode = versionNode.Children[0];

int size = versionNode.Size;
if (size <= FileVersionInfo.DataOffset)
return null;

byte[] buffer = ArrayPool<byte>.Shared.Rent(size);
try
{
int count = versionNode.GetData(buffer);
Span<byte> span = new Span<byte>(buffer, 0, count);
FileVersionInfo result = new FileVersionInfo(span);
return result;
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}

private ResourceEntry CreateResourceRoot()
{
return new ResourceEntry(this, null, "root", false, RvaToOffset(ResourceVirtualAddress));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Text;

namespace Microsoft.Diagnostics.Runtime.Utilities
Expand Down Expand Up @@ -35,6 +35,18 @@ public class ResourceEntry
/// </summary>
public bool IsLeaf { get; }

/// <summary>
/// Returns the size of data for this node.
/// </summary>
public int Size
{
get
{
GetDataVaAndSize(out _, out int size);
return size;
}
}

/// <summary>
/// The number of children this entry contains.
/// </summary>
Expand Down Expand Up @@ -86,24 +98,36 @@ public byte[] GetData()
return result;
}

/// <summary>
/// Get the data associated with this entry.
/// </summary>
/// <param name="span">The location to write the data</param>
/// <returns>The number of bytes actually read from the image and written to dest</returns>
public int GetData(Span<byte> span)
{
GetDataVaAndSize(out int va, out int size);
if (size == 0 || va == 0)
return 0;

return Image.Read(va, span);
}

/// <summary>
/// A convenience function to get structured data out of this entry.
/// </summary>
/// <typeparam name="T">A struct type to convert.</typeparam>
/// <param name="offset">The offset into the data.</param>
/// <returns>The struct that was read out of the data section.</returns>
public T GetData<T>(int offset = 0) where T : struct
public unsafe T GetData<T>(int offset = 0) where T : unmanaged
{
byte[] data = GetData();
int size = Marshal.SizeOf(typeof(T));
if (size + offset > data.Length)
throw new IndexOutOfRangeException();

GCHandle hnd = GCHandle.Alloc(data, GCHandleType.Pinned);
T result = (T)Marshal.PtrToStructure(hnd.AddrOfPinnedObject(), typeof(T));
hnd.Free();

return result;
int size = Unsafe.SizeOf<T>();
GetDataVaAndSize(out int va, out int sectionSize);
if (va == 0 || sectionSize < size + offset)
return default;

T output;
int read = Image.Read(va + offset, new Span<byte>(&output, size));
return read == size ? output : default;
}

private ResourceEntry[] GetChildren()
Expand Down

0 comments on commit b5fcdb7

Please sign in to comment.