Jump to Navigation

Essay

This document will explain some of the background theories I used to setup my libc5/glibc2 compilation environment. It may help you understand better why things are configured as they are, and how you can extend this setup with other compilation targets.

I had two goals in mind when I created this setup. First, it should be easy to compile for another target. I do not like to modify Makefiles to seek out every invocation of gcc/as/ld/nm/... in order to add some additional options. So I should be able to use the same Makefile for any target. The default cross-compiler installation of gcc, for example, installs the cross compiler as something like i486-linux-gcc, for example. Which means you would have to edit your Makefile.

Second, I wanted to keep the targets completely separate. During the transition from libc4 (a.out) to libc5 (ELF), for example, the libc4 include file directory tended to be a link to the libc5 include file directory. This was all right as long as both packages were maintained, but before too long libc5 added many features and changes not found in libc4. So when you installed a new libc5 version, the libc4 include files changed too, and did no longer correctly reflect the features found in your actual libc4. Which could create all sorts of nasty side-effects.

I came up with the following arrangement. Each target got its own directory directly under /usr/. The libc5 target, for example, would use /usr/i486-linux-libc5/, while the libc6 target would use /usr/i486-linux-libc6/. In this way each target would have its own hierarchy, and it would be easy to add new targets. Even only slightly different targets would be possible: why not a target under /usr/i586-linux-libc5/, which would create code optimized for the Pentium?

Of course, there would be a default target. In my case, this is the i486-linux-libc5 target. These files are mirrored in /usr (actually, the originals currently are in /usr/, and symbolic links are found in /usr/i486-linux-libc5/. But this is easy to switch around). In fact, it should be fairly easy to switch your default target, though it is not as trivial as switching around the links in /usr/. No, I have not tried this yet - I will write a short HOWTO when I do. Questions about this are welcome, I need a guinea pig :-)

Now, if we want to generate code for another target, we simply set our PATH to find the utilities in /usr/target/bin/ first. And these utilities will only use files under /usr/target/ (so the compiler /usr/target/bin/gcc calls the linker /usr/target/bin/ld, which uses only libraries under /usr/target/lib/, etc.)

Things are actually a bit more complicated for the libc5/glibc2 setup than if you would add other targets. This is, of course, because we do not only want to compile things for both, but also be able to execute both. For which we need a dynamic linker, which adds a bit to the complexity of it all.

A typical compilation environment for C consists of the following elements:

  • The compiler: this turns .c files into .s files.
  • The assembler: this turns .s files into .o files.
  • The linker: this combines .o files and libraries into executables
  • Other binary utilities, like the archiver (for creating libraries).
  • The C-library - static versions are enough for cross-compilation, but dynamic ones are much nicer, if available for your target

For running the files, you may also need:

  • The dynamic loader (sometimes called dynamic linker), if you use dynamic libraries.

We will need the following directory structure under each /usr/target/ directory:

  • bin/: Here we place all cross-compiler tools, like the compiler, assembler, linker and other utilities. Note that the executables in this directory need to be executable on your current machine, so they themselves are libc5 (or glibc2) objects. They generate/modify objects of the target kind, though.
  • lib/: Here we place all target libraries.
  • include/: Here we place all include files belonging to installed target libraries.
  • Other directories, like man and info, containing documentation about the cross-compiler tools and installed libraries.

Note that the bin directory is not meant to contain programs compiled for the target model. This directory is only in our path if we want to compile for this target. Target executables are either not executable on the current machine (so you do not want to install them), or they are in special cases (like our glibc2/libc5), but in that case you want to install them in a regular place like /usr/bin/. With one exception: if you would recompile one of the cross-compiler tools in the /usr/glibc2-target/bin/ directory for glibc2 to make it a native glibc2 object, you would of course still place it in this directory.

Note that the files in the lib directory are the only real target objects which are installed. They are needed by the linker to produce executables.

OK, but how do you actually generate the cross-compiler tools and libraries? This is not as trivial is it may seem at first sight.

The tools need to be told to use specific directories. The compiler gcc, for example, calls other utilities like the assembler and (optionally) the linker. It has to be told to use /usr/target/bin/ for this, instead of /usr/bin/. And the linker has to use libraries found in /usr/target/lib, not those in /usr/lib/, even if they have the same name and the version in /usr/lib seems to be more recent. They are not mutually compatible, after all...

Fortunately, most of the path-problems are solved of you tell during the configuration process of the utilities that they are 'cross- utilities'. For the GNU programs, this is done by specifying different host and target names.

Well, while we are talking about GNU programs: they have a rather fixed idea of the names which may be given to targets. They do not really understand i486-linux-libc5, for example. Fortunately, in newer versions, it is possible to use alias-names which solve this problem. But why did I not use the 'real' GNU names? Ah, but they have a tendency to change over time. i486-linux once referred to a libc5-based target, and now it is rather a libc6-based target. And I would not be surprised if a long time ago it was a libc4 target...

Then there is the default target, of course. This is what often is called the 'native' target, though the distinction blurs a bit with my setup and both libc5 and glibc2 installed. This target has some special properties, like additional search paths for libraries and include files (not only use /usr/target/include/, but also /usr/include/ and /usr/local/include/). This is nice, as you have probably a setup with many things in these directories, and moving them would be a real nuisance. Excluding the search-paths, there is no real difference between native and cross tools - except of course for the fact that the cross tools generate another kind of object than they are themselves. But if you can execute both, you do not want (or have) to be aware of this. These search-paths are the real reason, by the way, that changing default targets is not trivial. Together with the fact that for the native tools, gcc looks in /usr/bin/ instead of /usr/default-target/bin/.

When you have installed everything, you can change your compilation environment by adding /usr/target/bin/ at the beginning of your PATH (do not forget to export the variable again!). If you are configuring things, you can now say that your host machine is your current target! GNU configuration programs, at least, are intelligent enough to realize they are still cross-compiling, and will not try to execute target programs during the configuration process.

One of the reasons there are symbolic links to all programs from /usr/default-target/bin/ to /usr/bin/ is that you can now add /usr/default-target/bin/ at the front of your path and expect to use the correct tools. Without these links (thinking ie. that /usr/bin/ is somewhere in your path anyway) that would probably fail.

I will end this document with a small run-down to show you how to add additional targets to this scheme. One useful candidate would be a DJGPP-target, which would generate DOS-compatible executables. You could even run them with DOSEMU! You should use my document for installing glibc2 as a guide, as I will skim over many steps here.

First you would need the precompiled DJGPP libc. If you do not have it, you are in a bit of a catch-22 situation. Gcc needs access to this library when you prepare it as a cross-compiler (it uses several functions intern in a rather weird way), and you need the cross-gcc to compile the library! There are sometimes ways to solve this, but just getting the precompiled library will save you many a headache. You should install the library itself in /usr/DJGPP/lib/, and its include files under /usr/DJGPP/include/ (of course, use your own invented name for the target instead of the DJGPP here).

Next, you will want to compile a cross-version of the assembler, linker and other utilities. For this, you do not need the cross- compiler (or even the library). After all, the utilities are just libc5 (or even glibc2) objects, which happen to modify DJGPP objects. So get the source distribution of binutils, apply the DJGPP patches and compile it. You will probably want to apply some patches to enable target aliases too. You can use my patch as an example of how to do this, if it does not apply cleanly. Install these utilities in /usr/DJGPP/bin/.

Now it is time to prepare a cross-compiler. Get the compiler sources, add the DJGPP patches and configure it for a DJGPP target. Wow! That is really all you had to do! Install the binaries in /usr/DJGPP/bin/. Some gcc files will go into /usr/lib/gcc-lib/DJGPP/; this is all right.

Your compilation environment is now complete. You can set your path to /usr/DJGPP/bin to enter the environment.

Finally, you can recompile the C-library. The best way to do this is to enter the DJGPP environment and configure it as if you are on a real DJGPP (ie. DOS) machine.



Historic_content | by Dr. Radut