microsoft / clrmd

Microsoft.Diagnostics.Runtime is a set of APIs for introspecting processes and dumps.
MIT License
1.05k stars 254 forks source link

Cannot get PEImage of some assemblies in a Linux dump #891

Closed kevingosse closed 3 years ago

kevingosse commented 3 years ago

Getting the PEImage of some assemblies inside of a Linux memory dump fail, even though the PE header should be valid (at least I believe so).

As a simple repro, I create a console application with .NET 5:

namespace temp
{
    class Program
    {
        static void Main(string[] args)
        {
            _ = new System.Net.Http.HttpClient(); // Just to load additional dependencies

            Console.WriteLine("Hello World!");
            Console.ReadLine();
        }
    }
}

Getting the PE header of temp.dll fails with the error "System.IO.EndOfStreamException: 'Unable to read beyond the end of the stream.'" :

    System.Private.CoreLib.dll!System.IO.BinaryReader.InternalRead(int numBytes)    C#
    System.Private.CoreLib.dll!System.IO.BinaryReader.ReadInt32()   C#
    System.Reflection.Metadata.dll!System.Reflection.PortableExecutable.CorHeader.CorHeader(ref System.Reflection.PortableExecutable.PEBinaryReader reader) C#
    System.Reflection.Metadata.dll!System.Reflection.PortableExecutable.PEHeaders.PEHeaders(System.IO.Stream peStream, int size, bool isLoadedImage)    C#
    System.Reflection.Metadata.dll!System.Reflection.PortableExecutable.PEReader.InitializePEHeaders()  C#
    System.Reflection.Metadata.dll!System.Reflection.PortableExecutable.PEReader.PEHeaders.get()    C#
>   Microsoft.Diagnostics.Runtime.dll!Microsoft.Diagnostics.Runtime.Utilities.PEImage.PEImage(System.IO.Stream stream, bool leaveOpen, bool isVirtual) Line 104 C#
    Microsoft.Diagnostics.Runtime.dll!Microsoft.Diagnostics.Runtime.ModuleInfo.GetPEImage() Line 64 C#

Both the DOS header check and the PE signature check are successful, so I assume the PE header should be valid.

Note that it might be an issue with System.Reflection.PortableExecutable.PEReader rather than ClrMD itself. Or it could be the ReadVirtualStream. I'm trying to figure out exactly what's wrong, but maybe somebody already has an idea.

mikem8361 commented 3 years ago

It may be a file vs loaded layout issue with is controlled with the isVirtual flag passed to the ModuleInfo constructor. There isn't an easy way to determine the correct layout other than trying both. On Windows it is always "loaded" layout. It is something I want to try in clrmd (with Lee's ok) when I get time.

Can you try the latest dotnet-dump/SOS from master dump the module correctly? modules -v is the command. The module service tries both layout types and uses whatever returns a valid header, version, IsManaged flag, etc.

https://github.com/dotnet/diagnostics/blob/57a9d3d628cc87fbaef512c49dd5c8b9fb260a0e/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs#L211

kevingosse commented 3 years ago

Can you try the latest dotnet-dump/SOS from master dump the module correctly? modules -v is the command. The module service tries both layout types and uses whatever returns a valid header, version, IsManaged flag, etc.

Does it need to be the version from master? With the one currently installed I get:

/root/temp/bin/Release/net5.0/temp.dll
    Address:   00007FC95FBF1000
    FileSize:  00002000
    TimeStamp: 00000000
    IsManaged: True

The PEReader fails when trying to read address 0x00007fc95fbf3008. This indeed seems to be outside of the module range.

mikem8361 commented 3 years ago

The released dotnet-dump doesn't try both layouts.

kevingosse commented 3 years ago

Got it, gonna give it a try.

In any case, I confirm that switching the value of isVirtual in the ModuleInfo constructor for that module fixes the issue, so I think you're on the right track.

kevingosse commented 3 years ago

With the dotnet-dump built from master, I get:

35 /root/temp/bin/Release/net5.0/temp.dll
    Address:         00007FC95FBF1000
    ImageSize:       00006000
    IsPEImage:       True
    IsManaged:       True
    IsFileLayout:    True
    IndexFileSize:   32768
    IndexTimeStamp:  3308427098
    Version:         1.0.0.0
    PdbInfo:         48166658-5a5e-4728-9767-5747633809b0 1 /root/temp/obj/Release/net5.0/temp.pdb
    BuildId:         <none>
kevingosse commented 3 years ago

Trying both works for me, I'll be keeping that workaround for now (I'm using a custom version of ClrMD for this project anyway)

        public PEImage? GetPEImage()
        {
            try
            {
                var image = new PEImage(new ReadVirtualStream(DataReader, (long)ImageBase, IndexFileSize), leaveOpen: false, isVirtual: _isVirtual);

                if (image.PEHeader == null)
                {
                    image = new PEImage(new ReadVirtualStream(DataReader, (long)ImageBase, IndexFileSize), leaveOpen: false, isVirtual: !_isVirtual);
                }

                if (!_isManaged.HasValue)
                    _isManaged = image.IsManaged;

                return image.IsValid ? image : null;
            }
            catch
            {
                return null;
            }
        }

Thanks for the help 👍

leculver commented 3 years ago

Since we don't have a good way to detect this, I think the workaround you posted here is likely what we'll go with (trying both layouts). I'll include this in the next release of ClrMD.