Assemble a Small .NET Program from IL with Mono ilasm

ilasm turns a Common Intermediate Language (CIL) source file into a runnable .NET executable or library, no compiler in between. This guide takes you through assembling one, running it under Mono, and inspecting a file without executing it. Allow about 15 minutes if Mono is already installed. The examples use the ilasm shipped by Mono 6.8.0.105 from Ubuntu's mono-devel package.

Before you start

You need a shell, a text editor, Mono's ilasm, and a directory where you can create build outputs. The commands below do not need sudo. Do not assemble directly over a deployed application until you have checked the input and output paths: /output: replaces any existing file already sitting at that path.

Checkpoint: confirm which assembler you are about to use.

$ command -v ilasm
/usr/bin/ilasm
$ ilasm --version
Mono IL assembler compiler version 6.8.0.105

This installation also reports package version mono-devel 6.8.0.105+dfsg-3.6ubuntu2. The manpage documents an ilasm2 command for assemblies using version 2.0 features such as generics, but this installation does not ship an ilasm2 executable at all. Check your own package before relying on that name.

1. Create a minimal IL source file

Save this as hello.il. It declares the standard library, names the assembly, marks Main as the entry point, loads a string, calls Console.WriteLine, and returns:

.assembly extern mscorlib {}
.assembly Hello {}

.method public static void Main() cil managed
{
    .entrypoint
    ldstr "Hello from IL"
    call void [mscorlib]System.Console::WriteLine(string)
    ret
}

IL is low-level and punctuation-sensitive. A missing brace, a wrong method signature, or an unresolved assembly reference is enough to stop assembly dead. Keep the first test small, so any error points at a manageable input.

2. Assemble an executable

Use /exe and give the output an explicit name. Mono accepts options starting with either / or -; the slash form mirrors the manpage:

$ ilasm /exe /output:hello-ilasm.exe hello.il
Assembling 'hello.il' , no listing file, to exe --> 'hello-ilasm.exe'

Operation completed successfully

The assembler creates a managed executable. Building it does not run it, so inspect or test the file before putting it anywhere that matters.

Checkpoint: run the result through the installed runtime.

$ mono hello-ilasm.exe
Hello from IL
$ file hello-ilasm.exe
hello-ilasm.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly

3. Build a library instead

Use /dll when the output is meant for another managed program to reference. This only changes the image type; it does not turn a source file with an entry point into a useful library API on its own:

$ ilasm /dll /output:hello-ilasm.dll hello.il
Assembling 'hello.il' , no listing file, to dll --> 'hello-ilasm.dll'

Operation completed successfully
$ file hello-ilasm.dll
hello-ilasm.dll: PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly

For a real library, replace the demonstration Main method with public methods another assembly can actually call. Keep the executable and library names distinct while testing, because each invocation writes to the path given after /output:.

4. Scan source without making a runnable image

When you are investigating syntax or tokenisation, /scan_only scans the IL and prints tokens. It is handy for checking what the parser sees, but it is no substitute for a successful executable or library build:

$ ilasm /scan_only hello.il
Assembling 'hello.il' , no listing file, to exe --> 'hello.exe'
310 : .assembly
378 : extern
258 : mscorlib
267 : {
268 : }
310 : .assembly

The exact token listing depends on the source and this particular Mono release. Do not write scripts that depend on individual token numbers unless you have pinned the assembler version.

5. Turn on focused diagnostics

If a source is accepted but you need to understand its metadata, add /show_method_def or /show_method_ref. Add /show_tokens for a broader parser trace. These print diagnostics while assembling; they do not repair malformed IL for you:

$ ilasm /show_method_def /exe /output:hello-ilasm.exe hello.il

Keep diagnostic output separate from the command's actual success status. A successful build ends with Operation completed successfully. If the assembler reports an error instead, do not run a stale executable left over from an earlier build: choose a new output path while debugging, or check the output timestamp and exit status first.

6. Handle failures and clean up safely

For a syntax error, read the reported source location and compare braces, directives, method signatures, and referenced type names. For a missing input file, check the path rather than adding privileges. For an output error, confirm the directory exists and that you can write there. Nothing in this manpage installs an assembly or changes a system service.

If a test has left files you no longer need, remove only those explicitly named files from your test directory. If an output has replaced a valuable file, restore it from your normal backup or build artefact instead: ilasm has no undo operation of its own. Do not delete a directory recursively just to clear one failed build.

Warning: signing is a separate security decision. The /key:KEYFILE option signs with a full strong-name key pair, while /key:@CONTAINER uses a named key container. Treat both the key file and container access as sensitive. Do not leave private key material in a shared source directory or paste it into a command log.

Done means