164

I'm learning C#, so I made a little C# program that says Hello, World!, then compiled it with mono-csc and ran it with mono:

$ mono-csc Hello.cs
$ mono Hello.exe
Hello, World!

I noticed that when I hit TAB in bash, Hello.exe was marked executable. Indeed, it runs by just a shell loading the filename!

Hello.exe is not an ELF file with a funny file extension:

$ readelf -a Hello.exe
readelf: Error: Not an ELF file - it has the wrong magic bytes at the start
$ xxd Hello.exe | head -n1
00000000: 4d5a 9000 0300 0000 0400 0000 ffff 0000  MZ..............

MZ means it's a Microsoft Windows statically linked executable. Drop it onto a Windows box, and it will (should) run.

I have wine installed, but wine, being a compatibility layer for Windows apps, takes about 5x as long to run Hello.exe as mono and executing it directly do, so it's not wine that runs it.

I'm assuming there's some mono kernel module installed with mono that intercepts the exec syscall/s, or catches binaries that begin with 4D 5A, but lsmod | grep mono and friends return an error.

What's going on here, and how does the kernel know that this executable is special?


Just for proof it's not my shell working magic, I used the Crap Shell (aka sh) to run it and it still runs natively.


Here's the program in full, since a commenter was curious:

using System;

class Hello {
  /// <summary>
  ///   The main entry point for the application
  /// </summary>
  [STAThread]
  public static void Main(string[] args) {
    System.Console.Write("Hello, World!\n");
  }
}
11
  • 4
    While you got answer, But I am little bit confused, If you run the commands like php hello.php or python hello.py or perl hello.pl or in case of compiled language like java java hello they would also run as their are no executable there but a program read and execute file. However if you run ./hello.exe instead of mono hello.exe the I found your question and accepted answer more reasonable (which in case definitely use binfmt-support. Commented Feb 3, 2016 at 7:16
  • @kuldeep.kamboj I'm not sure I understand what you're saying but I was just surprised that a Windows executable runs"natively" on a Linux box.
    – cat
    Commented Feb 3, 2016 at 12:24
  • 1
    Actually what I was saying that any code file (or compiled file ) can be executed via its program in format program codefile, in your case program is mono, while my examples includes php, python, perl and java. I suspect if mono allow extension to other than .exe file still executed via code. It should not depend at all on windows as finally this is execute a compiled code. However if your source file have some dependent code like windows only APIs then obviously you must need wine for running that exe file. Commented Feb 3, 2016 at 13:04
  • 5
    Part of the wonderful irony of this is that /etc/magic or /usr/share/file/magic (or similar magical location) is the file that contains the information necessary to be able to do this.
    – user46212
    Commented Feb 3, 2016 at 16:12
  • 1
    @cat its a very interesting file (if I was to have a favorite file, it would probably be my first choice). It has the information for identifying all sorts of files. But you need to identify what the file is first before you can figure out how to run it. That's what file and its magic does. A similar thing - Java Binary Kernel Support for Linux from awhile back so you could do $ foo.jar rather than $ java -jar foo.jar - similar to what is done for mono.
    – user46212
    Commented Feb 3, 2016 at 16:19

1 Answer 1

206

This is binfmt_misc in action: it allows the kernel to be told how to run binaries it doesn't know about. Look at the contents of /proc/sys/fs/binfmt_misc; among the files you see there, one should explain how to run Mono binaries:

enabled
interpreter /usr/lib/binfmt-support/run-detectors
flags:
offset 0
magic 4d5a

(on a Debian system). This tells the kernel that binaries starting with MZ (4d5a) should be given to run-detectors. The latter figures out whether to use Mono or Wine to run the binary.

Binary types can be added, removed, enabled and disabled at any time; see the documentation above for details (the semantics are surprising, the virtual filesystem used here doesn't behave entirely like a standard filesystem). /proc/sys/fs/binfmt_misc/status gives the global status, and each binary "descriptor" shows its individual status. Another way of disabling binfmt_misc is to unload its kernel module, if it's built as a module; this also means it's possible to blacklist it to avoid it entirely.

This feature allows new binary types to be supported, such as MZ executables (which include Windows PE and PE+ binaries, but also DOS and OS/2 binaries!), Java JAR files... It also allows known binary types to be supported on new architectures, typically using Qemu; thus, with the appropriate libraries, you can transparently run ARM Linux binaries on an Intel processor!

Your question stemmed from cross-compilation, albeit in the .NET sense, and that brings up a caveat with binfmt_misc: some configuration scripts misbehave when you try to cross-compile on a system which can run the cross-compiled binaries. Typically, detecting cross-compilation involves building a binary and attempting to run it; if it runs, you're not cross-compiling, if it doesn't, you are (or your compiler's broken). autoconf scripts can usually be fixed in this case by explicitly specifying the build and host architectures, but sometimes you'll have to disable binfmt_misc temporarily...

1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .