eXtended FLAT (XFLAT) is a binary format that will support dynamic shared libraries on MMU-less Linux (uClinux) platforms. XFLAT is a mature suite of development tools including compiler, linker, loader, dynamic loader and support tools
FLAT (bFLT) is a binary format supported by the Linux (or uClinux) kernel. It is used frequently with MMU-less, uClinux systems.
The GOT (Global Offset Table) is used in most ELF programs to relocate global variables and function entry points. The GOT is a table that lies at a known, fixed offset from the code in a virtual address space: Any code can add an offset to the PC to get the address of the GOT and, from the GOT, get the relocated address of a function or a variable. That GOT then holds an offset to the linked data that may reside in a another module.
XFLAT does not support a GOT for two reasons, one historical and one technical. The historical reason is that embedded systems have usually taken extreme measures to conserve on memory and CPU cycles. By eliminating the GOT, you can save both storage space and one level of indirection when each variable or function is referenced. XFLAT was developed in the early uClinux days when this conservativism was taken very seriously.
A second more technical reason is that the GOT must lie in a region of address space that is contiguous with the code address space. If the code is going to be shared, then the text space must be common for all processes that use the module. The data space, on the other hand, must be unique for each process. In particular, the GOT bindings must be unique for every process. Process architectures that have MMUs can solve this problem by using virtual offsets: The same virtual address offset from the shared code space can map to different, physical data spaces. But this cannot be done if the architecture has no MMU.
In the XFLAT model, each module consists of two physical address spaces: the shared code space (I-space) and the process-specific data space (D-space). There is no way in this model to determine the address of the process-specific D-space as an offset from the common code I-space.
See above for a description of the GOT.
The GOT is used to bind and relocate functions and data. XFLAT provides an alternative mechanism to bind and relocate functions using a thunk layer that is inserted between each inter-module function call. However, without a GOT it is not possible to bind and relocate data. In short, with no GOT XFLAT cannot support sharing of global variables between program and shared library modules.
As described above and elsewhere, the global variables cannot be shared between your program code and shared libraries. Instead, the xflat-gcc tool inserts calls to data accessor functions to manage access to global variables across such module boundaries: When you reference such a global variable, you don't actually reference the variable, you actually call a function that returns a pointer to the variable that gets dereferenced to access the variable. See the discussion below for more detail.
As described above, the xflat-gcc tool inserts calls to data accessor functions to manage access to global variables across such module boundaries.
Typical dereferencing in this manner should not cause an measurable performance degradation if it is infrequent. However, such dereferencing could be a performance issue if such a variable is referenced at a high duty, say, in a "tight" loop. Then the small amount of processing required to access the variable could become a performance issue. In this case, you might want to save a pointer to the variable reference so that you do not call the accessor function at a high rate. For example, instead of:
#include <stdio.h>
...
int i;
for (i = 0; i < 10000; i++)
Try something like:
#include <stdio.h>
...
FILE *mystderr = stderr;
int i;
for (i = 0; i < 10000; i++)
Then, the stderr accessor function is called only once and outside of the "tight" loop.
Using such accessor functions has been a common practice with uClibc (stdout, stderr, etc. were already really function calls). XFLAT just required that this practice be extended.
Suppose your shared library exports a global variable something like:
foo_t foo;
Where foo_t would be any type and foo could be any name. The you write an accessor function like:
foo_t *get_foo(void) { return &foo; }
You would build this into your shared library. It is now an exported function that can be called across shared library boundaries. Then, in your program, change code like:
foo_t *foo = &foo;
foo.bar = 1;
foo_t bar = foo;
to:
foo_t *foo = get_foo();
(*get_foo()).bar = 1;
foo_t bar = (*get_foo());
Notice that in every case except for the first, the following macro will do the job with no code changes:
/* Instead of extern foo_t foo, use */
extern foo_t *get_foo(void);
#define foo (*get_foo())
First, you have to understand the differences between uClinux and Linux. There are lots of good summaries out there. Here are some I found by Googling:
The main differences is that uClinux doesn't support fork(). It does support support vfork() but vfork() is not a direct replacement for fork() (look at 'man vfork'). Basically the usage differences are:
As a result, any logic in existing code that uses fork() will, most likely, have to be completely redesigned to use vfork().
So, the first step to porting anything is first, check if it contains fork() calls. If it does, then you will have a big job on your hand getting it work work under uClinux.
Second, check if someone else has already ported the code to uClinux. Use Google.
Creating an XFLAT module is really very simple: You just compile with xflat-gcc and link with xflat-ld. Add -shared to xflat-ld command line to build a shared library; add the stack size to the command line to build a program.
But, of course, there is really more to the job than that. You may have to make additional changes to the code to work under uClinux (see above). And you may have to make some changes to work with some pecularities of XFLAT. See, here and here, for examples.
Sometimes when you use header file include preprocessor statements like:
xflat-gcc will fail to find the include file. This failure is an issue with the way that xflat-gcc handles include pathes and can be fixed by changing the include preprocessor statement to:
See below for further information
The tool xflat-gcc is just a thin wrapper around the real GNU compiler (with name like arm-linux-gcc or armeb-linux-uclibc-gcc or some such thing). They are the same compiler. The only difference is that xflat-gcc mucks with the command line parameters that are passed to the real GNU compiler in order to make life easier for you.
As disccussed above here and here, because of the technical approach taken by XFLAT, there are issues with accessing global variables across shared module boundaries. This is not so much a consequence of the XFLAT implementation as it is a consequence of the absence of the GOT (Global Offset Table): The absence of a GOT means that XFLAT cannot support the indirection that you would need to relocate the data references.
XFLAT works around this limitation by (1) recognizing when your code attempts to access a well-known shared variable, (2) replacing the variable reference with a call to an XFLAT accessor function that gets the variable address for you, and then (3) deferencing that address to access the global varialbe. Consider the following not-very-interesting code example called optind.c that includes getopt.h:
#include <getopt.h> int main(int argc, char **argv, char **envp) { return optind; }
You can compile this using the GNU compiler (armeb-linux-uclibc-gcc in this case) like:
or with
But the result is not the same. Compare the pre-processor outputs from:
with the pre-processor output from:
When compiled with armeb-linux-uclibc-gcc, the output is what you would expect. The shared global variable is just returned as in the original C file. But with xflat-gcc, the program becomes:
int main(int argc, char **argv, char **envp) { return (*(int*)xflat_varptr(XFLAT_OPTIND)); }
That happened because xflat-gcc messed with the include pathes! Look at the output from:
The -v option tells xflat-gcc to be verbose. You will see that armeb_linux_uclibc_gcc is invoked the following arguments:
arg[ 0] = /home/gnutt/projects/xflat/buildroot/build_armeb_nofpu/staging_dir/bin/armeb-linux-uclibc-gcc arg[ 1] = -nostdinc arg[ 2] = -I. arg[ 3] = -I- arg[ 4] = -I/home/gnutt/projects/xflat/buildroot/build_armeb_nofpu/staging_dir/include/xflat arg[ 5] = -I/home/gnutt/projects/xflat/buildroot/build_armeb_nofpu/staging_dir/include arg[ 6] = -I/home/gnutt/projects/xflat/buildroot/build_armeb_nofpu/staging_dir/include/g++-3 arg[ 7] = -I/home/gnutt/projects/xflat/buildroot/build_armeb_nofpu/staging_dir/lib/gcc/armeb-linux-uclibc/3.4.5/include/ arg[ 8] = -E arg[ 9] = -v arg[10] = -o arg[11] = optind.i arg[12] = optind.c
The important thing here is that include/xflat appears after -I- and before the standard include pathes. So, when optind.c includes getopt.h, the preprocessor statment #include <getopt.h> does not get the header file you think (the one in the include directory), but instead the one in the include/xflat directory. The XFLAT version of the getopt.h header file looks like this (in part):
#ifndef _XFLAT_GETOPT_H_ #define _XFLAT_GETOPT_H_ #include_next <getopt.h> /* For optarg and optind */ #include <xflat_accessors.h> #undef optarg /* In case optarg is already a macro */ #define optarg (*(char**)xflat_varptr(XFLAT_OPTARG)) #undef optind /* In case optind is already a macro */ #define optind (*(int*)xflat_varptr(XFLAT_OPTIND)) #endif /* _XFLAT_GETOPT_H_ */
And that is the trick that let's the libc shared global variables work with your program. Look at the the files in include/xflat: xflat-gcc performs this same trick on numerous other variables exported from libc such as errno, stdout, etc. You can see the full list of such variables at ..include/xflat/xflat_accessors.h.
But consider the following code example:
#includeextern int optind; int main(int argc, char **argv, char **envp) { return optind; }
After preprocessing with xflat-gcc, this leads to:
extern int (*(int*)xflat_varptr(XFLAT_OPTIND)); int main(int argc, char **argv, char **envp) { return (*(int*)xflat_varptr(XFLAT_OPTIND)); }
Which will not compile. In order to get a clean compilation, you must leave off the extern int optind (even though man getopt says that it is required).
xflat-gcc performs this same trick on numerous other variables exported from libc and it does this without any special knowledge or code changes in the user code. But a few exported variables, like optarg, optind, and environ are more troublesome, primarily because the man page usage suggests that they be explicitly externed.
This mucking with the GNU gcc header file pathes also explains the include file pathe errors that were mentioned above: Those include path issues are the consequence of the parameters that are passed to GNU GCC from xflat-gcc. In particular the -nostdinc option and the -I- logic. The include error is an unwelcome side-effect of those careful controls over the GCC search pathes.