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

Implement FileVersionInfo for PEImage #412

Merged
merged 2 commits into from
Dec 13, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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" />
leculver marked this conversation as resolved.
Show resolved Hide resolved
<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)
leculver marked this conversation as resolved.
Show resolved Hide resolved
{
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