Introduction
A .NET program or library is a Portable Executable file with one extra directory: the CLR runtime header (data directory 14, described here). It points at the metadata, which says everything about the types, members and references of the file, and at the managed resources and the strong name signature. The code itself (CIL, the instruction set of the .NET runtime) sits in method bodies that the metadata locates by RVA. The format is Ecma International’s ECMA-335 (Common Language Infrastructure), partitions II and III, with Microsoft’s augments since.
The same metadata comes out of every .NET language: C#, F# and Visual Basic produce the same tables, with different names and some
different habits (F# unions and records, VB modules). Four kinds of file are described here: an assembly (a manifest, an
Assembly table row, and usually the code of the program), a module (.netmodule: metadata and code but no manifest, to be added to an
assembly), a Windows Runtime metadata file (.winmd: the same tables with a special version string, describing a platform API) and a
reference assembly (metadata with no method bodies). A portable PDB is a fifth file with the same
metadata layout and no PE wrapper.
In the explorer, a .dll or .exe that is a .NET assembly opens as a PE file; its CLR header node has a child
Managed code (.NET) that opens the whole file again as a level of its own, and Read as offers .NET assembly for any PE file.
.netmodule and .winmd files open here directly.
Structure
PE headersthe DOS header, the PE signature, the COFF and optional headers with their 16 data directories, and the section headersnative PECLR runtime header72 bytes: runtime version, where the metadata, the resources, the strong name signature and the vtable fixups are, the flags and the entry point tokendata directory 14Metadata rootthe signatureBSJB, a version string such asv4.0.30319and a list of streamsmetadata#~ or #-the tables: a header (row counts, heap index sizes), then each present table as fixed-size rowstables#Strings, #US, #GUID, #Blobthe heaps: names, string literals, GUIDs, and the signature and value blobs that table columns point intoheapsMethod bodies, resources, signatureIL method bodies (located by the MethodDef RVA), the managed resources, the strong name signaturein the sections
The metadata root and streams page is the container. The tables page lists every table with its columns; heaps and coded indexes says how a column holds an offset or a row of one of several tables; signatures decodes the blobs that describe types and members; manifest and references covers the assembly identity, what it references, the resources, the strong name; and flags lists the flag enumerations.
Runtime versions in the files
The metadata root carries a version string. The strings seen in real files, and in files built for this page with compilers for each runtime, are below; what a schema version means is only what the files and the specification show.
| Version string | Seen in files built for | Tables schema |
|---|---|---|
v1.0.3705 | .NET Framework 1.0 (25 real files) | 1.0, with CLR header version 2.0 |
v1.1.4322 | .NET Framework 1.1 (4 real files) | 1.0, with CLR header version 2.0 |
v2.0.50727 | .NET Framework 2.0, 3.0 and 3.5 (net20 and net35 targets; 434 real files) | 2.0, CLR header 2.5; the generic tables (GenericParam, MethodSpec, GenericParamConstraint) are part of 2.0 files |
v4.0.30319 | .NET Framework 4.x, and every .NET Core and .NET 5 or later build, netstandard2.0 (305 real files) | 2.0, CLR header 2.5 |
v4.0.20926 | one real file; a build number of a 4.0 pre-release (our reading) | 2.0, CLR header 2.5 |
The counts are the distinct assemblies among 771 found on this machine (one of them has no tables); all were read and compared with Microsoft’s own reader. A portable PDB writes the string PDB v1.0, and a Windows Runtime
file a string that starts WindowsRuntime. One real file carries just 2.0.0.0 as its version string: the root allows any text.
What the explorer shows
- The tree follows the bytes: PE headers, the CLR header, the metadata root, each stream, every table row by row with each column decoded (names from #Strings, GUIDs, signatures, coded indexes as the table and row they name, flags as their enumerations), each heap entry by entry, then the resources and the other blocks the CLR header points at.
- The assembly section gives the identity, the runtime version, the schema, the CLR flags, the entry point (named), the strong name state and the public key token.
- The types listing prints namespaces, types and members as C# declarations with their metadata tokens, resolving signatures: generics with constraints, nested types, custom modifiers,
properties and events with their accessors, constants,
externmethods. Options choose which members it lists. - Problems are listed, never refused: an index past the end of a heap, a coded index with an unused tag, a row that does not exist, streams that overlap, a table cut off by the end of the stream, a malformed signature.
Not described
Known, not yet covered
- Reference: the CIL instruction set and method bodies (headers, exception clauses), the layout of custom attribute values, declarative security permission sets, marshalling descriptors, the .resources format inside the managed resources, the ReadyToRun native header, single-file bundles and mixed-mode (C++/CLI) images. These are documented in ECMA-335 and the runtime’s design documents and are the next work.
- Viewer: method bodies appear as bytes inside the section; the custom attribute, permission set and marshalling blobs show as bytes; the managed resources are listed with their offsets and lengths but not decoded;
the type listing does not read the
CompilerGenerated,Nullable,Extensionor other attributes, so extension methods, nullable annotations and record types show as their plain compiled form. - Viewer: the tables AssemblyProcessor, AssemblyOS, AssemblyRefProcessor and AssemblyRefOS (obsolete) and the Ptr tables of the uncompressed stream were read only from the specification and from built files: no compiler here writes them.
Unknown
- Reference: how obfuscators and packers that break the rules (invalid indexes, overlapping heaps, extra streams) are read by each runtime: the explorer reports what it finds and goes on, but the runtime’s own rules for such files are not documented.
Also used for
- 16-bit Windows executable (NE) – Windows 3.x and early OS/2 programs and DLLs, in movable segments.
- OS/2 executable (LX) – 32-bit OS/2 programs and DLLs, in paged objects.
- Windows 64-bit executable (PE32+) – 64-bit Windows programs and DLLs (x64, ARM64), with a 64-bit optional header (magic 0x20B).
- Windows executable (PE32) – 32-bit Windows programs and DLLs, with a 32-bit optional header (magic 0x10B).
Links
Software
Developers
- System.Reflection.Metadata – Microsoft’s metadata reader and writer, the independent reader these pages were checked against (.NET)
- Mono.Cecil – library that reads and writes assemblies (.NET)
Reference
- ECMA-335 – the Common Language Infrastructure standard: partition II is the metadata
- ECMA-335 augments – the runtime repository’s corrections and additions to the standard
- Portable PDB metadata – the format of the debug tables and the #Pdb stream