Emulation: Role-Playing for Computers
Emulation is the process of allowing one machine to pretend to be another one. A variety of emulation options are available, depending on the system(s) involved. Which option is right for you and your situation? This article describes some of the possibilities available to you.
System Call Translation
System call translation is a very common form of emulation found in most Free *NIX systems and in some other places. In a UNIX-like system, the kernel handles a set of fairly well-defined system calls. A program marshals its arguments in a particular way, typically placing them in specific processor registers, and then executes a system call instruction. This causes the processor to switch to a higher privilege level and vector into the kernel’s system call handler, which constructs a stack frame and jumps to the function corresponding to the system call number.
The system call numbers for each system are well-defined. Once a system call has been added, changing the number would require all applications that use it to be recompiled, breaking backward compatibility. The numbers are not the same across operating systems, though. The read system call, for example, is number 3 on both Linux and FreeBSD, while the readv call is 120 on FreeBSD and 145 on Linux.
In some situations, it’s useful to be able to run software on FreeBSD that was actually written for Linux. Some proprietary software, for example, is supported only on Linux. Since Linux and FreeBSD both implement the POSIX specification, and both use ELF format binaries, it ought to be easy, but it isn’t.
The first problem comes from the fact that, on x86, Linux uses the MS-DOS system call convention and passes parameters in registers, while FreeBSD uses the UNIX convention, passing them on the stack. But both systems use the same mechanism for entering the kernel (raising interrupt 80h), and they both store the system call number in the EAX register.
Before you can run a Linux binary on FreeBSD, you have to "brand" it. When a Linux-branded binary is running, the kernel uses a small amount of wrapper code when a system call is generated. This approach inspects the system call number as normal, but instead of vectoring into the correct system call handler, it just calls a wrapper function, which then calls the equivalent FreeBSD system call handler.
For many POSIX system calls, this branding is quite easy. Actions such as reading and forking are well-defined, and have the same arguments on any system. For others, it’s less easy. If one of the parameters of a function is a structure that’s not fully defined by the specification, the wrapper may need to translate between Linux and FreeBSD formats before processing the call, and then translate the other way afterward. In the worst case, the functionality may be implemented very differently on the two systems, requiring the wrapper to contain an almost-complete implementation of the call.
This form of emulation is typically very fast. The time spent performing the translation is usually 1–2% of the time spent completing the system call.