Published: 24 Aug 2009
By: Granville Barnett

In this part of the series “looking at CIL” we explore the structure of a managed module, the link between managed modules and assemblies, and key tools used to disassemble and compile CIL code.

Contents [hide]

Introduction

In the previous part of the series we covered the role of CIL, compilation strategies, the instruction sets that CIL comprises of, and finally verification. This part of the series focuses on managed modules, assemblies, and a few tools that can be used to disassemble and compile CIL.

Managed Modules

A managed module consists of two very important sections:

  • Metadata: describes the layout of classes, members, attributes, etc.
  • Managed code: CIL methods, global data, etc. (This is optional.)

The astute reader will notice that I have left out two other bits of information that also form a managed module: portable executable (PE) header (Windows. Informs Windows which version of the OS it needs to run, and other things like whether or not it uses a command line interface (CUI), or graphical user interface (GUI), among other things); and the CLR header (this determines which version of the CLR should be loaded into the process). In this article we will ignore the PE and CLR headers and concentrate on the metadata and managed code sections of the managed module.

The class loader at some stage in the future will use the metadata of the managed module in order to layout the class and its members appropriately in memory. Metadata also provides great flexibility to the programming model. For example, tools like Visual Studio can use a managed modules metadata to fuel IntelliSense. Other core services provided by the CLR and Mono also use metadata. For example, the garbage collector uses metadata to track object references.

High-level Compilers

Compilers like C# emit managed modules for you. However, the CLR and Mono do not deal with managed modules. Rather, they deal with assemblies. An assembly is the lowest form of reuse within .NET. It provides facilities for versioning and enforcing security amongst other things. An assembly comprises of two things: one or more managed modules, and a manifest. The manifest describes what managed modules and resources the assembly comprises of.

Most programs consist of an assembly that contains a single managed module, this is the default. For example, when you compile your average C# program the C# compiler emits a managed module and then builds an assembly for you that consists of that managed module. In order for an assembly to comprise of more than one managed module one has to use the assembly linker to link the relevant managed modules and resources together. The resulting assembly contains a manifest to denote what managed modules and resources it comprises of.

Tools

There exists a number of tools which ship with both Microsoft .NET and Mono that allow us to both disassemble assemblies, as well as compile CIL code. In order to disassemble an assembly we would use either ildasm (IL Disassembler) or monodis (Mono Disassembler). It seems obvious but I will point it out anyway: ildasm ships with the commercial Microsoft .NET; and monodis ships with the open source Mono project.

In order to compile CIL code we can use the ilasm tool. (ilasm is the name for the CIL compiler that ships with both Microsoft .NET and the Mono project.) Just like any other compiler that targets .NET you feed in some source code, in this case written in CIL, and ilasm generates an assembly for you. Because we are authoring programs at a lower-level, than say ones authored in C#, there are a number of things that we need to explicitly state ourselves when writing CIL which we are oblivious to when authoring programs in a higher-level language.

The following two sections look at using the CIL disassembler and compiler tools. As noted previously both ildasm and monodis do the same thing, the reader can use the two tools interchangeably.

Disassembling a program

In this section we will disassemble a trivial C# program that comprises of a few interesting properties:

  • It defines both static and local variables;
  • Methods defined in the BCL are called, as well as one we have defined ourselves;
  • A parameter is passed to a method by value; and
  • One of the methods returns a value.

No doubt the reader is somewhat mesmerized by the complexity of this program, however, its trivial nature will provide a somewhat sturdy foundation to help keep future discussions about the CIL it is composed of both concise and simple.

Listing 1: Our amazing program

Compile the previous listing and then either use ildasm or monodis to dump out the disassembly. On Mono using monodis the following is dumped out to the terminal.

Listing 2: Disassembly of our program

We will now look at each segment of CIL and discuss what is going on.

Listing 3: The program header

The previous code listing shows the program header. It states that our assembly references mscorlib, specifically mscorlib with the version number 1:0:5000:0 and public token key (B7 7A 5C 56 19 34 E0 89). Next we define some assembly metadata that can be used to identify our assembly, in this case the identify of our assembly is Part2. The hash algorithm is not important to our discussion, however, it does exist. When I compiled the program I did not specify a version for the assembly so we are stuck with the default version 0:0:0:0. The final part of the program header consists of the name of the module, in our case it is an executable by the name of Part2.exe.

Listing 4: Defining a class

The next line within our disassembly defines a class by the name of Program. Unlike higher-level languages like C#, in CIL we must explicitly state that we are deriving from System.Object. Also note that System.Object is prefixed by [mscorlib]. This is the assembly that the System.Object type can be found in. beforefieldinit states that the types initializer method will be executed sometime before a static field is first accessed for that type.

Listing 5: Our static field

Not much to say here, our field (denoted by the keyword .field) is private, static, and initonly which states that only an instance constructor can write to this field.

Listing 6: The default constructor for Program

The first method we come across is a special one, this is our default (notice that it is marked as such, i.e. .ctor is marked with the keyword default) constructor for our Program class. An instance constructor is always marked with the instance, specialname, and rtspecialname keywords.

Progressing further on through the method we see that at most we can have eight items on the stack at any one time throughout the lifetime of this methods execution. The next line (ldarg.0) pushes the this pointer onto the stack, the following CIL then invokes the default constructor for the this pointer on the stack.

All constructors have a void return type. However, notice that we have a ret opcode emitted as the final CIL instruction for our constructor method. In CIL every method explicitly returns to the caller through the emission of a ret instruction irrespective of whether we are actually returning some meaningful value (which we are not in this case as the stack is empty).

Listing 7: The Main entrypoint method

There are a few new keywords in our entrypoint method Main, one of which, coincidentally, is the entrypoint keyword. At least one method within your program must be defined as being the entrypoint method. Further on we initialize a single slot within the methods local variable list, in this case a 32 bit integer. The local variable myNumber will be stored within the initialized slot of the locals list for the Main method. MyNumber is assigned the value 3 by first pushing the constant 4 byte integer 3 onto the stack and then storing that value into index 0 of Mains local list (there is only one slot, as we initialized only one slot at the beginning, i.e. .locals init …). After we have stored the local myNumber into index 0 of Main's local list we then need to push it onto the stack once more as we pass this value to the Program::Square(int32) method. When the Square method is invoked it expects a 32 bit integer to be on the stack. Just before the Square method returns control to the caller (in our case the Main method) it will push a 32 bit integer onto the stack, as indicated by its signature. Notice that we do not have to prefix the call to Square with an assembly name. This is because Square is defined within the Part2 assembly. The final method call to WriteLine uses the value pushed onto the stack by the call to the Square method (a 32 bit integer) as its parameter. Finally we return control to the caller.

Listing 8: Square method

Our final method, Square, pushes the value at index 0 of the methods argument list onto the stack twice. The multiply operation (mul) pops these two values off the stack and then pushes the result of the operation onto the stack. Unlike the previous methods we have looked at thus far there actually exists an item on the stack when we return to the caller. The value on the local stack will be pushed onto the callers stack, which is the case we covered earlier in the Main method (i.e., the state of the stack just before we called WriteLine).

Compiling CIL code

In this section we will compile the code that was generated for us from the previous sections disassembly. Please save the output of the disassembly into a file named Part2.il, then run the ilasm tool passing in Part2.il as the source file – ilasm Part2.il. The previous command results in the generation of Part2.exe, which is semantically equivalent to that of our original C# program.

Summary

In this part of the series covering CIL we looked briefly at the structure of a managed module and assembly, and the tools used to both disassemble and compile CIL code.

In general the things to takeaway are as follows: a managed module comprises of metadata, and optionally managed code (CIL); an assembly comprises of one or more managed modules, and is the lowest form of reuse within .NET (i.e., it provides versioning, security, etc.); and an assembly can comprise of more than one managed module, but in order to do so you need to use the assembly linker.

<<  Previous Article Continue reading and see our next or previous articles Next Article >>

About Granville Barnett

Sorry, no bio is available

This author has published 32 articles on DotNetSlackers. View other articles or the complete profile here.

Other articles in this category


Developing a Hello World Java Application and Deploying it in Windows Azure - Part I
This article demonstrates how to install Windows Azure Plugin for Eclipse, create a Hello World appl...
Android for .NET Developers - Building a Twitter Client
In this article, I'll discuss the features and capabilities required by an Android application to ta...
Ref and Out (The Inside Story)
Knowing the power of ref and out, a developer will certainly make full use of this feature of parame...
Developing a Hello World Java Application and Deploying it in Windows Azure - Part II
In this article we will see the steps involved in deploying the WAR created in the first part of thi...
Android for .NET Developers - Using Web Views
In this article, I'll show a native app that contains a web-based view. The great news is that HTML ...

You might also be interested in the following related blog posts


Issue 50 - DotNetNuke 5 Admin Modules Access, Reset Logins, iPhone OWS read more
How to Limit Access to Administrator Modules in DotNetNuke 5 - 5 Videos read more
New Release: BGI SCORM LMS Portal v2.5.0 - New Certification Manager with WYSIWYG Certificate Designer - DotNetNuke Integrated read more
Utilizing the Microsoft AJAX Framework and ClientAPI to Develop Rich Modules: Part III read more
Install the URL Rewrite Module for IIS 7.0 RTW today! read more
Creating a Setup Project for IIS Extensions using Visual Studio 2008 read more
What's New in DNN User Manual for DNN 4.9+ read more
Modifying IIS 7.0 Administration.config using Javascript and AHADMIN read more
Shadow Modules in DotNetNuke using SQL read more
Cambrian First Look - Framework Changes (Pt 1) read more
Top
 
 
 

Please login to rate or to leave a comment.

Free Agile Project Management Tool from Telerik
TeamPulse Community Edition helps your team effectively capture requirements, manage project plans, assign and track work, and most importantly, be continually connected with each other.