DLL Inspector

2010-01-31

Since I’ve been experimenting with 32- and 64-bit DLLs, along with the different /clr compiler options, I decided to write a quick utility to help me see how they differ. It has a small GUI:

I used this utility to build a bunch of dlls with various compiler options. I’ll post about that shortly. Here is the code for DllInfo, if anyone is interested. There is still some work to do, but what I’ve got so far served my purpose well enough:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.Runtime.InteropServices;
using System.IO;

// TODO: About DllInfo...
// better display in currentFileBox

// Based on Kris Stanton's code at http://blogs.msdn.com/kstanton/archive/2004/03/31/105060.aspx

namespace DllInfo
{
    public enum MachineType
    {
        Native = 0,
        I386 = 0x014c,
        Itanium = 0x0200,
        x64 = 0x8664
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct IMAGE_DOS_HEADER
    {
        [FieldOffset(60)]
        public int e_lfanew;    // byte offset to the NT header
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct IMAGE_NT_HEADERS32
    {
        [FieldOffset(0)]
        public uint Signature;
        [FieldOffset(4)]
        public IMAGE_FILE_HEADER FileHeader;
        [FieldOffset(24)]
        public IMAGE_OPTIONAL_HEADER32 OptionalHeader;
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct IMAGE_NT_HEADERS64
    {
        [FieldOffset(0)]
        public uint Signature;
        [FieldOffset(4)]
        public IMAGE_FILE_HEADER FileHeader;
        [FieldOffset(24)]
        public IMAGE_OPTIONAL_HEADER64 OptionalHeader;
    }

    public struct IMAGE_FILE_HEADER {
        public ushort Machine;
        public ushort NumberOfSections;
        public ulong TimeDateStamp;
        public ulong PointerToSymbolTable;
        public ulong NumberOfSymbols;
        public ushort SizeOfOptionalHeader;
        public ushort Characteristics;
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct IMAGE_OPTIONAL_HEADER32
    {
        [FieldOffset(0)]
        public ushort Magic;
        [FieldOffset(208)]
        public IMAGE_DATA_DIRECTORY DataDirectory;
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct IMAGE_OPTIONAL_HEADER64
    {
        [FieldOffset(0)]
        public ushort Magic;
        [FieldOffset(224)]
        public IMAGE_DATA_DIRECTORY DataDirectory;
    }

    public struct IMAGE_DATA_DIRECTORY
    {
        public uint VirtualAddress;
        public uint Size;
    }

    public partial class DllInfoForm : Form
    {
        String currentDll = null;
        bool validDll = false;
        String machineType = null;
        bool isManaged = false;
        int moduleCount;
        bool hasAssembly;

        public DllInfoForm()
        {
            InitializeComponent();
            infoPanel.Paint += new PaintEventHandler(panelOnPaint);
        }

        void panelOnPaint(object obj, PaintEventArgs pea)
        {
            Graphics g = pea.Graphics;
            Brush brush = new SolidBrush(ForeColor);

            g.DrawString("valid?: ", Font, brush, 0, 0);
            g.DrawString(currentDll == null ? "" : validDll.ToString(), Font, brush, 100, 0);
            g.DrawString("machine: ", Font, brush, 0, 20);
            g.DrawString(machineType, Font, brush, 100, 20);
            g.DrawString("managed?: ", Font, brush, 0, 40);
            g.DrawString(validDll ? isManaged.ToString() : "", Font, brush, 100, 40);
            g.DrawString("has assembly?: ", Font, brush, 0, 60);
            g.DrawString(validDll ? hasAssembly.ToString() : "", Font, brush, 100, 60);
            g.DrawString("module count: ", Font, brush, 0, 80);
            g.DrawString(validDll ? moduleCount.ToString() : "", Font, brush, 100, 80);
            // TODO: is it IL or binary?
        }

        void updateDisplay() {
            if (currentDll == null)
            {
                currentFileBox.Text = "";
            }
            else
            {
                currentFileBox.Text = currentDll;
            }
            infoPanel.Invalidate();
        }

        private void chooseFile(object sender, EventArgs ea)
        {
            OpenFileDialog d = new OpenFileDialog();
            d.Filter = "dll and exe files (*.dll, *.exe)|*.dll;*.exe|All files (*.*)|*.*";
            d.Title = "Select a dll";
            if (d.ShowDialog() == DialogResult.OK)
            {
                currentDll = d.FileName;
                try
                {
                    loadDll(currentDll);
                    updateDisplay();
                }
                catch (BadImageFormatException)
                {
                    MessageBox.Show("bad image format exception");
                }
            }
        }

        public void loadDll(String dll)
        {
            byte[] data = new byte[4098];
            FileInfo fi = new FileInfo(dll);
            FileStream f;
            int n;
            ushort m;

            try
            {
                f = fi.Open(FileMode.Open, FileAccess.Read);
                // TODO: better to catch file-opening exceptions and show the user the problem.
            }
            catch
            {
                validDll = false;
                return;
            }

            n = f.Read(data, 0, 4096);
            f.Flush();
            f.Close();
            f.Dispose();

            // We must have read at least far enough to read dos_header->e_lfanew
            // Marshal.OffsetOf warns that the managed struct offsets may differ from the unmanaged,
            // but this is only true if the data types are not blittable.
            // Since C# lets me obtain points to these structs, I know that they are blittable,
            // so I can disregard the warning about Marshal.OffsetOf.
            if (n < (int)Marshal.OffsetOf(typeof(IMAGE_DOS_HEADER), &quot;e_lfanew&quot;) + sizeof(int))
            {
                validDll = false;
                return;
            }

            unsafe
            {
                fixed (byte* d = data)
                {
                    IMAGE_DOS_HEADER *dos_header = (IMAGE_DOS_HEADER*)d;

                    // Make sure we read enough bytes to follow e_lfanew and read everything else:
                    // Use the 64-bit values since they are a few bytes larger.
                    // If we&#39;re running on 32 bits, there will still be other bits in the dll after the header,
                    // so we won&#39;t reject any false positives.
                    if (n e_lfanew
                        + (int)Marshal.OffsetOf(typeof(IMAGE_NT_HEADERS64), "OptionalHeader") // +24
                        + (int)Marshal.OffsetOf(typeof(IMAGE_OPTIONAL_HEADER64), "DataDirectory") // +224
                        + (int)Marshal.OffsetOf(typeof(IMAGE_DATA_DIRECTORY), "Size")  // +4
                        + sizeof(uint)) // +4
                    {
                        validDll = false;
                        return;
                    }

                    IMAGE_NT_HEADERS32* nt32_header = (IMAGE_NT_HEADERS32*)(d + dos_header->e_lfanew);

                    m = nt32_header->FileHeader.Machine;

                    validDll = Enum.IsDefined(typeof(MachineType), (Int32)m);
                    if (validDll)
                    {
                        machineType = ((MachineType)m).ToString();


                        // 0x10b indicates a PE32 assembly
                        // 0x20b indicates a PE32+ assembly (i.e. 64-bit, hence either x64 or Itanium)
                        if (nt32_header->OptionalHeader.Magic == 0x20b)
                        {
                            isManaged = ((IMAGE_NT_HEADERS64*)nt32_header)->OptionalHeader.DataDirectory.Size > 0;
                        }
                        else
                        {
                            isManaged = nt32_header->OptionalHeader.DataDirectory.Size > 0;
                        }
                    }
                }
            }

            if (validDll)
            {
                Assembly a;
                Module mod;
                PortableExecutableKinds peKind;
                ImageFileMachine machine;

                try
                {
                    a = Assembly.LoadFile(dll);
                    hasAssembly = true;
                    moduleCount = a.GetModules().Length;
                    // TODO: sanity check that PEKind matches what we inferred from the binary?
                    /*
                    m = a.GetModules()[0]; // TODO: multi-module assemblies?
                    m.GetPEKind(out peKind, out machine);
                    dllType = peKind.ToString();
                    machineType = machine.ToString();
                      */
                }
                catch (BadImageFormatException)
                {
                    // This is not quite right:
                    // The file may have an assembly but be opened on the wrong platform (32 vs 64 bits).
                    hasAssembly = false;
                    moduleCount = 0;
                }
                catch (FileLoadException)
                {
                    hasAssembly = true;
                    moduleCount = -1;
                }

            }
        }
    }
}
blog comments powered by Disqus Prev: Password Dictionary Variations Next: Enabling 64-bit Support in Visual Studio 2008