Linux Compatibility on BSD for the PPC Platform
Pages: 1, 2, 3, 4
Some other syscalls do not work the same way on different architectures,
due to different argument sizes or different argument transmission
mechanisms (in registers vs on stack). For some of them, there are
already alternative implementations of the wrapper function. For
instance, a call to mmap() is implemented by linux_sys_mmap() on the
Alpha, and it is implemented by linux_old_mmap() on the i386.
Now, the idea is to get a good but not perfect syscalls.master, and to
fix problems as they arise later. So once syscalls.master looks good, we
build the linux_syscallargs.h, linux_syscalls.h by typing "make" in
sys/compat/linux/arch/powerpc, and we can start trying to build a
kernel.
Building the first kernel
Now when we try to build a kernel, of course it will fail, because most of the required source code is still missing, but the idea is that a failed build will tell us which gaps to fill.
First, we want to tell the config(8) tool that we added files to
the kernel. Here we will work on the NetBSD/macppc port, but everything
remains true for other ports. In the sys/arch/macppc/conf directory, we
have a file called files.macppc that lists the files used to build a
kernel for macppc. In order to modularize the compatibility code in the
kernel, we will just add two include statements. These statements will
tell config(8) to include the file describing what is needed for the machine-independent part of Linux compatibility (sys/compat/linux/files.linux),
and the file describing what is needed for the machine-dependent part
(sys/compat/linux/arch/powerpc/files.linux_powerpc):
# Linux binary compatibility (COMPAT_LINUX)
include "compat/linux/files.linux"
include "compat/linux/arch/powerpc/files.linux_powerpc"
There is also the OSS audio compatibility framework, which is required in order to link a kernel with Linux compatibility. This is included by the following lines:
# OSS audio driver compatibility
include "compat/ossaudio/files.ossaudio"
We then have to create the latter files.linux_powerpc file, and fill it
with all the source files created in sys/compat/linux/arch/powerpc so far.
Again, the idea is just to grab the i386 version of that file from
sys/compat/linux/arch/i386, and to comment out or remove every line
referencing files that are not yet in the powerpc directory.
Then we can add the COMPAT_LINUX option to our favourite kernel config
file, and start a kernel build. (If you need some documentation, please
read the documentation here).
Of course it will fail; we expected it. During the various failures, we
can discover that the source code in sys/compat/linux/common needs a lot
of macros prefixed with LINUX_ and a lot of typedefs and struct
definitions prefixed by linux_. The idea is always the same: to
grab the i386 version of the file containing the requested
macro/typedef/struct definition, and to adapt it for the PowerPC.
During this work, the linux/include/asm-ppc and linux/include/linux
directories from the Linux kernel sources will be useful. It is
essential to avoid just copying the i386 version of the different files
needed in the powerpc directory such as linux_termios.h or
linux_types.h. There are very few differences between most of the i386
version and the PowerPC version of the Linux includes we need to define,
but a careful check of every value will avoid lots of trouble finding
out what went wrong later.
After adding a lot of header files, the sys/compat/linux/arch/powerpc
directory starts looking like its i386 counterpart. There are only a few
.c files missing. We then have to define a few functions that are
defined in i386/linux_machdep.c and i386/linux_ptrace.c, else the kernel will not build. Most of the linux_machdep.c file holds functions related to signal delivery, whereas the linux_ptrace.c file holds functions that
enable Linux's gdb use on emulated binaries. Obviously, we don't need
most of this now. So the idea is to write empty functions that just
return zero without actually doing anything. The goal is to have a
kernel that builds, and to add the missing code later.
Remember that each time a .c file is added to the
sys/compat/linux/arch/powerpc directory, it has to be added to
sys/compat/linux/arch/powerpc/files.linux_powerpc, and then, the
config(8) utility must be rerun. This integrates the new file into the
kernel build process. Otherwise, the new file will be ignored.
Matching the Linux binaries
Once we have a working kernel, we can try our first Linux binary on it. To
do this, we go on a LinuxPPC machine and compile the following
program, linked as a static binary. This is done using the -static flag
with gcc.
/*
* hello.c -- A hello world test
* Build with gcc -static -o hello hello.c
*/
#include <stdio.h>
int main (int argc, char **argv) {
printf ("Hello world!\n");
return 0;
}
Then we try to run the compiled binary on the NetBSD system. Normally, it shouldn't work. Most likely, we get a strange message explaining that a syntax error occurred after a "(", and this sounds like the kernel decided this was a shell-script and gave it to the shell to execute. The dynamic version should just crash, but we will take care of it later.
Our problem is that the kernel was not able to recognise the executable
as a Linux binary. This can be outlined by running ktrace(1) and
kdump(1) on the executable. If the kernel had matched the
executable as a Linux binary, then the kernel trace should contain a
EMUL "linux" record.
So we have to get a working Linux binary-matching mechanism. When
starting a new binary (on execve() calls), the NetBSD kernel performs
some probe tests to find out what to do. Practically, the kernel
maintains a list of struct execsw (struct execsw is defined in
sys/sys/exec.h) describing the available ways of executing a program:
native ELF, native a.out, shell scripts, Linux emulation, and so on.
This list is initialized from sys/kern/exec_conf.c, and is used in
sys/kern/kern_exec.c. A member of the struct execsw is a pointer to a
probe function, whose job is to return 0 if it matches the executable.
For Linux ELF32 emulation, this function is linux_elf32_probe(), which
is implemented in sys/compat/linux/common/linux_exec_elf32.c.