In last month's column, we focused on the Java Class Loader. Today, we will take a brief look at Microsoft's Managed Execution Process, which is C#'s answer to protecting the integrity of downloaded code.
There are four steps to the Managed Execution Process:
- Design the source code.
- Compile the source code into Microsoft Intermediate Language (MSIL) and metadata.
- Compile MSIL to native code. A just-in-time (JIT) compiler does this during the program's execution time, turning MSIL into processor-specific code.
- Execution, managed by the Common Language Runtime (CLR).
Source code comes out of the C# compiler as MSIL. All .NET compilers emit IL, whose safety was designed around certain scenarios (particularly executing Web-based code or code from a net share).
MSIL is CPU-independent and includes instructions for loading, storing, initializing, and calling methods on objects. It also carries arithmetic and logical operations, control flow, direct memory access, exception handling, and other operations. It understands object types and has instructions that raise and catch exceptions. It can create and initialize objects, call virtual methods on objects, and indirectly manipulate array elements.
When the compiler produces MSIL, it also produces metadata. Metadata describes the types in the code, including a definition of each type, the signature of each type's members, the members of the code references, and other data the runtime uses during execution. The MSIL and metadata are contained within a Portable Executable (PE) that is based on and extends the Microsoft PE and Common Object File Format (COFF). This file format enables the operating system to recognize common language runtime images. Metadata allows your code to describe itself, which takes away the need for type libraries or interface definition language (IDL). Runtime extracts this metadata as needed during execution. The MSIL assembler generates the PE from the MSIL assembly language.
Just in Time
Unlike Java, a virtual machine does not interpret MSIL. Instead, IL is converted to native code when the application loads by a JIT compiler. Once this happens, the executed code is native. This basically changes the hardware-independent MSIL into CPU-specific code. The runtime provides a JIT compiler for each architecture it supports.
|Unlike Java, C# has the option of being unsafe. Code can be given permission to bypass verification, classes and methods can contain pointers, and garbage collection can be turned off.|
The JIT wouldn't be 'in time' unless it did this on the fly. Instead of converting the entire MSIL (that is in the PE) into native code, it converts it as needed during execution and stores it for subsequent calls. This obviously reduces the time it takes the JIT to compile and execute code.
Code must also pass through a JIT verification process unless an administrator in the domain has established a policy that bypasses it (code that bypasses verification is considered 'unsafe'). The verification examines the MSIL and the metadata to find out whether the code is type safe.
Type safety boils down to:
- Code accesses only memory locations that it is authorized to.
- Code accesses memory and call methods through properly defined types.
- Inspection to verify that the MSIL was correctly generated.
This enforces rules so that C# cannot create invalid references, cast between integer and reference types, or run typical buffer overflow attacks. Array ranges are checked so that they cannot be overwritten, dynamically allocated objects and arrays are initialized to zero, and arithmetic operations are checked so that they cannot overflow target variables or objects.
The program startup occurs when the executing environment calls the designated method, or entry point, which is always Main.
The .NET runtime CLR doesn't run like Java's virtual machine, instead the IL is now translated into native code that then runs as a program, which the CLR monitors. It is similar to Java 2 in that the runtime handles SecurityExceptions that can deny program requests if it has not been assigned appropriate permissions via a Security Policy. The Security Policy questions assemblies at load time, focusing mainly on where the code came from, and who authorized it.
The CLR uses code access security, which controls access to protected resources and operations. It grants and denies permissions within method calls. This enforces runtime restrictions by comparing the granted permissions of each call to the permissions the caller should have. It helps prevent luring attacks where less trusted code calls highly trusted code to perform illegal operations. The CLR keeps track of all assemblies, where they come from, and what security restraints should be placed on them, and it provides a number of other services such as automatic memory management (garbage collection) and debugging support.
Unlike Java, C# has the option of being unsafe. Code can be given permission to bypass verification, classes and methods can contain pointers, and garbage collection can be turned off. Code that is considered unsafe can attempt to execute if the local security policy, set by an administrator, allows it. By default, code that is unsafe is only allowed to run if it originates on the local machine. C# allows you to write pointers by using the unsafe keyword on a class or method, which marks the IL as unsafe (defined at an assembly level) and can then only be run within a fully trusted environment.
- Eric Gunnerson is Microsoft's test lead for the C# compiler, and his MSDN articles and Website were extremely informative when muddling through Managed Execution Process.
- Trupin, Joshua. "C# Offers the Power of C++ and Simplicity of Visual Basic." MSDN Magazine. Sept. 2000.
- Richter, Jeffry. "Microsoft .NET Framework Delivers the Platform for an Integrated, Service-Oriented Web." MSDN magazine, Sept. 2000.
- The MSDN library.
- The C# language specification.
About the Author
Thomas Gutschmidt is a freelance writer, in Bellevue, Wash., who also works for Widevine Technologies.