Why do I need a Cross Compiler?: Difference between revisions

Update rationale for cross-compilation with recommendations and problems that happen without one
[unchecked revision][unchecked revision]
(Update rationale for cross-compilation with recommendations and problems that happen without one)
Line 1:
You need to use a [[GCC_Cross-Compiler|cross-compiler]] ''unless'' you are developing on your own operating system. The compiler ''must'' know the correct target platform (CPU, operating system), otherwise you will run into trouble. You may be able to use the compiler that comes with your system if you pass a number of options to beat it into submission, but this will create a lot of completely imaginary problems.
 
It is possible ask your compiler what target platform it is currently using by calling the command:
 
gcc -dumpmachine
 
If you are developing on 64-bit Linux, then you will get a response such as 'x86_64-unknown-linux-gnu'. This means that the compiler thinks it is creating code for Linux. If you use this gcc to build your kernel, it will use your system libraries, headers, the Linux libgcc, and it will make a lot of problematic Linux assumptions. If you use a [[GCC_Cross-Compiler|cross-compiler]] such as i585-elf-gcc, then you get a response back such as 'i586-elf' that means the compiler knows it is doing something else and you can avoid a lot of problems easily and properly.
 
= How to build a Cross-Compiler =
{{Main|GCC Cross Compiler}}
 
It is easy and takes a few moments to [[GCC_Cross-Compiler|build a cross-compiler]] that targets your operating system. It may take a while to build it on slower computers, but you only need to do it once, and you save all the time you would otherwise spend on "fixing" the completely imaginary problems you would encounter otherwise. Later on, when you start building a user-space for your operating system, it is worth creating an [[OS_Specific_Toolchain|OS Specific Toolchain]] for absolute control of the compiler and to easy compiling user-space programs.
 
= Transitioning to a Cross-Compiler =
Perhaps you have not been using a [[GCC_Cross-Compiler|cross-compiler]] until now, in which case you are likely doing a lot of things wrong. Unfortunately, a lot of kernel tutorials suggest passing certain options and doing things in a manner that potentially causes a lot of trouble. This section documents some of the things you should watch out for. Please read this section carefully and point others to it if you see them using troublesome options.
 
== Linking with ld ==
You shouldn't be invoking ld directly. Your cross-compiler is able to work as a linker and it does a lot of really nice stuff to your object files before calling ld itself. If you get weird errors during compilation, use your cross-compiler for linking and it may go away. If you do need ld, be sure to use the cross-linker (i586-elf-ld) rather than the system linker.
 
== Using cross-tools ==
You get a lot of useful programs when you build your cross-binutills. For instance, you get i586-elf-readelf, i586-elf-as, i586-elf-objdump, i586-elf-objcopy, and more. These programs know about your operating system and handle everything correctly. You can use some the programs that come with your local operating system instead (readelf, objcopy, objdump) if they know about the file format of your operating system, but it is in general best to use your cross tools instead. These tools all consistently have the prefix 'i586-elf-' if the platform of your OS is i586-elf.
 
== Options that you should pass to your Compiler ==
You need to pass some special options to your compiler to tell it it isn't building user-space programs.
 
=== -ffreestanding ===
This is important as it lets the compiler know it is building a kernel rather than user-space problem. The documentation for GCC says you are required to implement the functions memset, memcpy, memcmp and memmove yourself in freestanding mode.
 
=== -nostdlib (same as both -nostartfiles -nodefaultlibs) ===
The -nostdlib option is the same as passing both the -nostartfiles -nodefaultlibs options. You don't want the start files (crt0.o, crti.o, crtn.o) in the kernel as they only used for user-space programs. You won't want the default libraries such as libc, because the user-space versions are not suitable for kernel use. You should only pass -nostdlib, as it is the same as passing the two latter options that you can then remove.
 
=== -lgcc ===
You disable the important libgcc library when you pass -nostdlib (-nodefaultlibs). The compiler needs this library for many operations that it cannot do itself or that is more efficient to put into a shared function. You must pass this library near the end of the link line, after all the other object files and libraries, or the linker won't use it and you get strange linker errors.
 
=== -mno-red-zone (x86_64 only) ===
You need to pass this on x86_64 or interrupts will corrupt the stack. The red zone is a x86_64 ABI feature that means that signals happen 128 bytes further down the stack. Functions that use less than that amount of memory is allowed to not increment the stack pointer. This means that CPU interrupts in the kernel will corrupt the stack. Be sure to pass enable this for all x86_64 kernel code.
 
=== -fno-exceptions, -fno-rtti (C++) ===
It is wise to disabled C++ features that doesn't work out-of-the-box in kernels. You need to supply a C++ support library to the kernel (in addition to libgcc) to make all C++ features work. If you don't use these C++ features, it should be sufficient to pass these options.
 
== Options that you shouldn't pass to your Compiler ==
There is a number of options you normally shouldn't pass to your cross-compler when building a kernel. Unfortunately, a lot of kernel tutorials suggest you use these. Please do not pass a option without understanding why it is needed and don't suggest to people that they use them. Often, these options are used by those that don't use cross-compilers to cover up other problems.
 
=== -m32, -m64 (compiler) ===
If you build a cross-compiler such as i586-elf-gcc, then you don't need to tell it to make a 32-bit executable. Likewise, you don't need to pass -m64 to x86_64-elf-gcc. This will make your Makefiles much simpler as you can simply select the correct compiler and things will work. You can use x86_64-elf-gcc to build a 32-bit kernel, but it's much easier to just build two cross-compilers and use them. In addition, using a cross-compiler for every CPU you target will make it easy to port third-party software without tricking it into passing -m32 as well.
 
=== -melf_i386, -melf_x86_64 (linker) ===
You don't need to pass these for the same reason as -m32 and -m64. Additionally, these options are for ld, and you shouldn't be invoking ld directly in the first place, but rather linking with your cross-compiler.
 
=== -32, -64 (assembler) ===
The cross-assembler (i586-elf-as) defaults to the platform you specified when building binutils, and so you don't need to repeat the choice here. You can use the cross-compiler as an assembler, but it is okay to call the assembler directly.
 
=== -nostdinc ===
You shouldn't pass this option as it disables the standard header include directories. However, you do want to use these headers as they contain many useful declarations. The cross-compiler comes with a bunch of useful headers such as stddef.h, stdint.h, stdarg.h, and more.
 
If you don't use a cross-compiler, you get the headers for your host platform (such as Linux) which are unsuitable for your operating system. For that reason, most people that don't use a cross-compiler use this option and then have to reimplement stddef.h, stdint.h, stdarg.h and more themselves. People often implement those files incorrectly as you need compiler magic to implement features such as stdarg.h.
 
=== -fno-builtin ===
You shouldn't pass this option as it disables default compiler builtins. If the compiler sees a function called 'strlen', it normally assumes it is the C standard 'strlen' function and it is able to optimize the expression strlen("foo") into 3 at compile time, instead of calling the function. This option has value if you are creating some really non-standard environment in which common C functions don't have their usual semantics.
 
=== -fno-stack-protector ===
I've see a lot of newbies pass this option. Upon closer examination, I see no reason to pass it, but I'm not entirely sure, perhaps it's because it requires libgcc? I'd leave the option out unless you actually need it. Please correct this description if you know more.
 
= Problems that occur without a Cross-Compiler =
You need to overcome a lot of imaginary problems to use your system gcc to build your kernel. You don't need to deal with these problems if you use a cross-compiler.
 
=== More complicated compilation commands ===
The compiler assumes it is targetting your local system, so you need a lot of options to make it behave. A trimmed down command sequence for compiling a kernel without a cross-compiler could look like this:
 
as -32 boot.s -o boot.o
gcc -m32 kernel.c -o kernel.o -ffreestanding -nostdinc
gcc -m32 my-libgcc-reimplemenation.c -o my-libgcc-reimplemenation.o -ffreestanding
gcc -m32 -T link.ld boot.o kernel.o my-libgcc-reimplemenation.o -o kernel.bin -nostdlib -ffreestanding
 
Actually, the average case is worse. People tend to add many more problematic or redundant options. With a real cross-compiler, the command sequence could look this this:
 
i586-elf-as boot.s -o boot.o
i586-elf-gcc kernel.c -o kernel.o -ffreestanding
i586-elf-gcc -T link.ld boot.o kernel.o -o kernel.bin -nostdlib -ffreestanding -lgcc
 
=== Reimplementing libgcc ===
You cannot use the host libgcc when building a kernel. The Linux libgcc has some nasty dependencies last timed I checked. The common case newbies run into is 64-bit integer division on 32-bit systems, but the compiler may generate such calls in many cases. You will often end up rewriting libgcc when you should have been using the real thing in the first place.
 
=== Rewriting freestanding headers (often incorrectly) ===
If you don't pass -nostdinc you get the target system headers (which is your local system if not using a cross-compiler), and that will cause a lot of problems in the non-cross-compiler case. You will end up rewriting the standard freestanding headers such as stdarg.h, stddef.h, stdint.h. and more. Unfortunately, as mentioned above, these headers need a bit of compiler magic to get just right. If you use a cross-compiler, all these freestanding headers can be used out of the box with no effort.
 
=== Complicated compiling user-space programs ===
You need to pass even more options to the command lines that build programs for your operating systems. You need a -Ipath/to/myos/include and -Lpath/to/myos/lib to use the C library, and more. If you set up an OS-specific toolchain, you just need
 
i586-myos-gcc hello.c -o hello
 
to cross-compile the hello world program to your operating system.
 
=== Compiler releases break your OS ===
Not everyone is using the same gcc as your are, which means that people on other operating systems (even versions, or compiler releases) will have trouble building your operating system correctly. If you use a cross-compiler, then everyone is using the same compiler version and assumptions about the host system won't make it into your operating system.
 
=== Support ===
You will have a much easier time getting support from the operating system development community. Properly using a cross-compiler shows that you have followed instructions and are at the same level as everyone else, and that your local system compiler isn't causing trouble.
 
=== And so on ===
As the project grows in size, it becomes much more complicated to maintain your operating system without a real cross-compiler. Even if your ABI is very much like Linux, your operating system isn't Linux. Porting third party software is near impossible without a cross-compiler. If you set up a real OS-specific toolchain and a sysroot of your OS, you can compile software just by giving --host=i586-myos to ./configure.
 
= Background information =
 
=== Where did the idea of cross compiling come from?===
 
Line 27 ⟶ 131:
Later still, when you're confident that you can both develop and use your kernel, from within your kernel, you'll want to stop building all your programs on a separate "development" machine. More specifically, at this point, you want to have your "build" machine be the same as your test machine such that you build programs that run on your OS, from within your OS. At this point, it's time to take the final step, and, from your separate "development" machine, build a compiler that will run on your OS (host = yourOsTargetArch-yourChosenFormat-yourOs), and target your OS (target = yourOsTargetArch-yourChosenFormat-yourOs). This cross compiler, the canadian cross, will allow you to natively compile other programs while running on your own OS's userspace. It is essentially a "distribution native compiler", like the one that comes with your distro. From that point on, having a compiler that runs on and targets your OS, you can essentially more freely port programs, and allow people to do the same while running your OS, assuming you package that native compiler with your OS.
 
=== When do I not need a cross-compiler? ===
<b>So yes, it is advised, for sanity's sake, to use a cross compiler even when your distro's native compiler targets almost the same machine as your standalone environment.</b>
If you create a real operating system and manage to port gcc to it, that gcc will produce the very same code as i586-myos-gcc. That means that you don't need a cross-compiler on your own operating system, because the gcc there will already do the right thing. This is why the Linux kernel is built with the Linux gcc, instead of a Linux cross-compiler.
 
=== A further concretion example ===
Line 51 ⟶ 156:
 
[[Category:FAQ]]
[[Category:OS_Development]]
[[Category:OS_theory]]
Anonymous user