Linux Compatibility on BSD for the PPC Platform: Part 2
Pages: 1, 2, 3, 4
Dumping the stack, you can see the parameters you give to the program
and its environment. Stackdump also give you the address of argc, which
is the place where the program stores argc on the stack. In fact, the
program copied that value from an upper address on the stack before
entering main(). If we do not get the appropriate value for argc, we must find out where the program gets its argc, and fix the way
the NetBSD kernel sets up the stack so that argc gets written where the
emulated binary expects it.
Note: This is a stack dump with the desired stack layout, not the original one.
argc=0x7fffe8a8
argv=0x7fffe8ac<snip>
7fffe8a0 7fff e8c0 0180 0744 0000 0001 7fff e904 ................
7fffe8b0 7fff e8b0 0000 0006 0184 0000 0184 0000 ................
7fffe8c0 7fff e8e0 0180 05cc 0000 0000 0000 0000 ................
7fffe8d0 7fff e8e0 4186 65e0 7fff e9e0 4186 5d60 ....A.e.....A.]'
7fffe8e0 7fff e8f0 4188 9580 7fff e9e0 4186 5d60 ....A.......A.]'
7fffe8f0 0000 0000 0000 0000 0000 0000 0000 0000 ................
7fffe900 0000 0001 7fff eab0 0000 0000 7fff eab5 ................
Next to this copied argc, here at 0x7fffe8a8, stands a pointer to
**argv, at 0x7fffe8ac. This is more interesting because looking at the
pointed address, at 0x7fffe904, we can find the **argv pointer that was
set up by the kernel. Next to it, at 0x7fffe900, we have the argc value
set up by the kernel. In this example, everything is fine, but if the
kernel does not set up argc at the place the executable expects it,
searching around the place pointed by the pointer to **argv (here at
0x7fffe8ac) is a good option.
When searching for the argc value set up by the kernel, the idea is to
look for an integer value (4 bytes on the PowerPC) equal to the actual
number of arguments given to the program (the program name itself being
the first argument, so that number is at least 1). Next to argc we
have **argv, which points to the *argv array. Each element of this array
is a pointer to a null terminated argument string, so it is easy to
identify.
We can figure out what the problem is by trying stackdump with various
arguments. On the PowerPC, the problem was that we needed to set up argc
on a 16-byte boundary. And there was a special trick if argc was
already to appear on a 16-byte boundary, because the emulated binary
then expected it to be 16 bytes lower on the stack.
To fix this problem, and get arguments passed to the program, we
need to modify the stack pointer before writing argc, **argv
and **envp on the stack. Setting up the stack is normally done by the
copyargs() function, which lives in sys/kern/kern_exec.c. But it is
possible to supply a customized copyargs() function by filling the
appropriate field of COMPAT_LINUX's struct execsw. This is done in
sys/kern/exec_conf.c, using the linux_copyargs_function macro. That
macro should be defined in sys/compat/linux/arch/powerpc/linux_exec.h.
Thus, by modifying this macro, we can use a customized copyargs()
function. The Alpha port of COMPAT_LINUX already did this. The
customized function is linux_copyargs(), and it is in the
sys/compat/linux/arch/alpha/linux_exec_alpha.c file.
Because there is already a linux_exec.c in sys/compat/linux/common, this file
cannot be called linux_exec.c, because when you build the kernel, all
object files fit in the same build directory. Having the same name twice
will result in the second object file overwriting the first one, and
this will lead to a link error. That file was intended to be architecture-independent, so we use the Alpha version
with some PowerPC add-ons. The result is the
sys/compat/linux/arch/powerpc/linux_exec_powerpc.c file, which is common
to the Alpha and the PowerPC platforms. It should be moved to the architecure-independent sys/compat/linux/common/linux_exec.c file later.
Linux_copyargs() first calls the standard copyargs() function, to set up all the argv and envp arrays. It leaves a linux_elf_aux_argsize bytes gap for the ELF auxiliary table (we will take a look at this later), and then it attempts to write argc, and the **argv and **envp pointers. The PowerPC-specific alignment is done by this code section:
#ifdef LINUX_SHIFT
/*
* Seems that PowerPC Linux binaries expect
* argc to start on a 16 bytes
* aligned address. And we need one more 16
* byte shift if it was already
* 16 bytes aligned.
*/
(unsigned long)stack = ((unsigned long)stack - 1) & ~LINUX_SHIFT;
#endif
The LINUX_SHIFT command is a macro, defined as 0x0000000fUL in
sys/compat/linux/arch/powerpc/linux_exec.h, and we use an ifdef test to prevent the Alpha version to do this PowerPC-specific fix that
would break NetBSD/Alpha Linux emulation. The file remains
architecture-independent.
With this fix, we managed to get statically linked executables to get their
arguments. However, a dynamically linked program will still fail because
ld.so does not find the ELF auxiliary table.
The ELF auxiliary table
The ELF dynamic linker (ld.so) needs more information than just argc, **argv, and **envp to actually link a program. It must be able to locate the ELF section where the list of shared libraries needed by the program is located. This kind of information is transmitted to ld.so by setting up the ELF auxiliary table on the stack. This table contains a few
entries, each containing two fields: type and value. The details of
each field are specified in the System V Release 4 PowerPC ABI, that
can be found here.




