A brief introduction to managed execution and CLI

Keywords: C# Windows github

Catalog

The processor cannot interpret the assembly directly. Assemblies use another language, the Common Intermediate Language (CIL), or intermediate language (IL) 1.

The C compiler converts the C source code file into an intermediate language. In order to convert CIL code into machine code that can be understood by the processor, an additional step (usually at run time) needs to be completed. This step involves an important element of C program execution: VES (Virtual Execution System). VES is also known as runtime.

It compiles CIL code as needed, a process called just in time compilation. If code is executed in the context of a "agent" such as "runtime", it is called managed code, and the process executed under the control of "runtime" is called managed execution.

It is called "hosting" because the "runtime" manages aspects such as memory allocation, security and JIT compilation, thus controlling the main program behavior. Code that does not require a "runtime" to execute is called native code or unmanaged code.

Note: "runtime" may refer to "program execution time" or "virtual execution system". For clarity, use "execution time" for "program execution time" and "runtime" for the agent responsible for managing the execution of C ා program. 2

The runtime specification is contained in a more inclusive specification, the CLI (Common Language Infrastructure) specification. As an international standard, CLI includes the following specifications:

  • VES or runtime.
  • CIL.
  • The type system supporting language interoperability is called CTS (Common Type System).
  • How to write guidelines for libraries accessed through CLI compatible languages, which are specifically placed in the Common Language Specification (CLS).
  • Metadata that enables services to be recognized by the CLI, including the layout or file format specification of the assembly.
  • Running in the context of the runtime execution engine, programmers can use several services and functions without writing code directly, including:

    • Language interoperability: interoperability between different source languages. Language compilers translate each source language into the same intermediate language (CIL) to achieve this interoperability.
    • Type safety: check conversion between types to ensure compatible types can be converted to each other. This helps prevent buffer overflows, which are the main cause of security concerns.
    • Code access security: proof that the assembly developer's code is authorized to execute on the computer.
    • Garbage collection: a memory management mechanism that automatically frees up the space allocated by the runtime for data.
    • Platform portability: the same assembly can run on multiple operating systems. To achieve this, an obvious limitation is the inability to use platform specific libraries. So the platform dependency problem needs to be solved separately.
    • BCL (base class library): provides a large code library that developers can rely on (in all. NET frameworks) so that they do not have to write the code themselves.

Note: This article is just a brief introduction to cli. The purpose is to familiarize the reader with the execution environment of C ා program. In addition, some terms used later in this series are also mentioned. When the time is right, I will summarize CLI and its relationship with C.

CIL and ILDASM

As mentioned earlier, the C ා compiler converts C ා code into CIL code instead of machine code. Processor only understands machine code, so CIL code must be converted into machine code before it can be executed by processor. You can use the CIL disassembler to deconstruct an assembly into CIL. This kind of CIL disassembler (ILDASM is short for IL Disassembler) is usually called by Microsoft's unique file name ILDASM. It can disassemble the assembly and extract the CIL generated by C compiler.

The result of disassembling a. NET assembly is easier to understand than machine code. Many developers fear that even if someone doesn't get the source code, the program can be disassembled and its algorithm exposed easily. In fact, whether based on CLI or not, the only safe way to prevent any program from decompilation is to prohibit access to compiled programs (for example, only store programs on the website, not distribute them to the user's machine).

However, if the purpose is only to reduce the possibility of others getting source code, some obfuscator products can be considered. This product opens up the IL code and transforms it into a form that is functional but more difficult to understand. This prevents ordinary developers from accessing the code, making it difficult for the assembly to be decompiled into easy to understand code. Unless the program needs advanced security protection for the algorithm, the obfuscator is enough.

View CIL output of myApp.dll

Please refer to this article for myApp.dll program: https://www.vinanysoft.com/c-sharp-basics/introducing/start-with-hello-world/

After Visual Studio is installed, ILDASM will be installed by default. The location is: C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A (any directory), \ bin\NETFX 4.8 Tools (any directory), \ x64.

Double click to run ildasm.exe, and drag myApp.dll in, as shown below:

Double click to view the IL code. The following is the IL code of MANIFEST

// Metadata version: v4.0.30319
.assembly extern System.Runtime
{
  .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )                         // .?_....:
  .ver 4:2:1:0
}
.assembly extern System.Console
{
  .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )                         // .?_....:
  .ver 4:1:1:0
}
.assembly myApp
{
  .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) 
  .custom instance void [System.Runtime]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78   // ....T..WrapNonEx
                                                                                                                   63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 )       // ceptionThrows.

  // ---The following custom properties are added automatically, do not uncomment-------
  //  .custom instance void [System.Runtime]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 ) 

  .custom instance void [System.Runtime]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 18 2E 4E 45 54 43 6F 72 65 41 70 70 2C 56   // ....NETCoreApp,V
                                                                                                              65 72 73 69 6F 6E 3D 76 33 2E 30 01 00 54 0E 14   // ersion=v3.0..T..
                                                                                                              46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C 61 79   // FrameworkDisplay
                                                                                                              4E 61 6D 65 00 )                                  // Name.
  .custom instance void [System.Runtime]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 05 6D 79 41 70 70 00 00 )                   // ...myApp..
  .custom instance void [System.Runtime]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 05 44 65 62 75 67 00 00 )                   // ...Debug..
  .custom instance void [System.Runtime]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 07 31 2E 30 2E 30 2E 30 00 00 )             // ...1.0.0.0..
  .custom instance void [System.Runtime]System.Reflection.AssemblyInformationalVersionAttribute::.ctor(string) = ( 01 00 05 31 2E 30 2E 30 00 00 )                   // ...1.0.0..
  .custom instance void [System.Runtime]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 05 6D 79 41 70 70 00 00 )                   // ...myApp..
  .custom instance void [System.Runtime]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 05 6D 79 41 70 70 00 00 )                   // ...myApp..
  .hash algorithm 0x00008004
  .ver 1:0:0:0
}
.module myApp.dll
// MVID: {29FC93A2-9A52-445C-A581-09AA5BCC11C7}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x000002015D090000

.class private auto ansi beforefieldint

.class private auto ansi beforefieldinit myApp.Program
       extends [System.Runtime]System.Object
{
} // end of class myApp.Program

.ctor:void()

.method public hidebysig specialname rtspecialname 
        instance void  .ctor() cil managed
{
  // Code size 8 (0x8)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  call       instance void [System.Runtime]System.Object::.ctor()
  IL_0006:  nop
  IL_0007:  ret
} // end of method Program::.ctor

Main:void(string[])

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size 42 (0x2a)
  .maxstack  2
  .locals init (valuetype [System.Runtime]System.DateTime V_0)
  IL_0000:  nop
  IL_0001:  ldstr      "Hello World!"
  IL_0006:  call       void [System.Console]System.Console::WriteLine(string)
  IL_000b:  nop
  IL_000c:  ldstr      "The current time is "
  IL_0011:  call       valuetype [System.Runtime]System.DateTime [System.Runtime]System.DateTime::get_Now()
  IL_0016:  stloc.0
  IL_0017:  ldloca.s   V_0
  IL_0019:  call       instance string [System.Runtime]System.DateTime::ToString()
  IL_001e:  call       string [System.Runtime]System.String::Concat(string,
                                                                    string)
  IL_0023:  call       void [System.Console]System.Console::WriteLine(string)
  IL_0028:  nop
  IL_0029:  ret
} // end of method Program::Main

At the beginning is the manifest information. It includes not only the full name of the module being decompiled (myApp), but also all modules and assemblies it depends on and their version information.

Based on such a CIL code list, the most interesting thing may be that it is relatively easy to understand what a program does, which is much easier than reading and understanding machine code (assembler).

The above code has an explicit reference to System.Console.WriteLine(). CIL code list contains a lot of peripheral information, but if developers want to understand the internal working principle of C ා module (or any CLI based program), but they can not get the source code, as long as the author does not use the obfuscator, it is relatively easy to understand such CIL code list.

In fact, some free tools (such as Red Gate Reflector, ILSpy, JustDecompile, dotPeek and CodeReflect) can decompile CIL to C automatically.

Use ILSpy to view the decompiled code of myApp.dll

ILSpy's address: https://github.com/icsharpcode/ILSpy

Double click to run ILSpy.exe, and drag myApp.dll in, as shown below:

Note: the difference between disassembly and decompile. Disassembly is the assembly code, and decompilation is the source code of the language used.

Original link: https://www.vinanysoft.com/c-sharp-basics/introducing/managed-execution-and-the-common-language-infrastructure/

  1. The third term for CIL is Microsoft IL (MSIL). This blog uses the term CIL because it is adopted by the CLI standard. C programmers often use the word IL when communicating, because they all assume that IL refers to CIL rather than other intermediate languages.

  2. Quotes are always used when runtime is used as a noun.

Posted by stenk on Tue, 10 Dec 2019 15:02:29 -0800