“Why do I need a Cross Compiler”的版本间差异

来自osdev
跳到导航 跳到搜索
(创建页面,内容为“:''注意:''此页面特定于GCC。 如果您使用另一个编译器,您应该研究通常如何使用该编译器进行交叉编译,并以这种方式进行。 GCC与本机目标系统的绑定非常紧密,而许多其他编译器则不然。 有些编译器甚至没有本机目标,它们始终是交叉编译器。 您需要使用U交叉编译器|交叉编译器'',除非''您是在自己的操作系统上开发的。 编译器“必须…”)
 
 
第1行: 第1行:
''注意:''此页面特定于GCC。 如果您使用另一个编译器,您应该研究通常如何使用该编译器进行交叉编译,并以这种方式进行。 GCC与本机目标系统的绑定非常紧密,而许多其他编译器则不然。 有些编译器甚至没有本机目标,它们始终是交叉编译器。
::''注意:'' 本页面是特定于GCC的。 如果你使用另一个编译器,你应该研究一下该编译器通常是如何进行交叉编译的,并且应该这样做。 GCC与其本机目标系统紧密绑定,许多其它编译器则没有。 有些编译器甚至没有本机目标,它们始终是交叉编译器。


您需要使用[[GCC|U交叉编译器|交叉编译器]]'',除非''您是在自己的操作系统上开发的。 编译器“必须”知道正确的[[Target Triplet | Target platform]](CPU、操作系统),否则您将遇到麻烦。 如果您传递了许多选项以使其提交,那么您可能可以使用系统附带的编译器,但这将产生许多完全不必要的问题。
‘’除非‘’你是在自己的操作系统上开发的,你需要使用[[GCC_Cross-Compiler|交叉编译器]]。 编译器「必须」知道正确的 [[Target Triplet| 目标平台]] (CPU,操作系统),否则会遇到麻烦。 如果你通过一系列选项来配置编译系统,你也许可以使用系统附带的编译器,但这会产生很多完全不必要的问题。


可以通过调用以下命令询问编译器当前使用的目标平台:
可以通过调用以下命令来询问编译器当前使用的目标平台是什么:


<source lang=“bash”>
<source lang="bash">
gcc-转储机器
gcc -dumpmachine
</source>
</source>


如果您是在64位Linux上开发的,那么您将得到一个响应,如“x86_64-unknown-Linux-gnu”。这意味着编译器认为它正在为Linux创建代码。 如果您使用这个GCC来构建内核,它将使用您的系统库、头文件、Linux[[libgcc]],并且会产生很多有问题的Linux假设。 如果您使用[[GCC_Cross-Compiler | Cross-Compiler]],例如i686 elf GCC,那么您会得到一个响应,例如“i686 elf”,这意味着编译器知道它正在做其他事情,您可以轻松、正确地避免许多问题。
如果你在64位Linux上进行开发,那么你将获得诸如 “x86_64-unknown-linux-gnu” 之类的响应。 这意味着编译器认为它正在为Linux创建代码。 如果你使用这个GCC来构建你的内核,它将使用你的系统库、头文件、LINUX [[libgcc]],并且它会做出很多有问题的LINUX环境假设。 如果你使用像i686-elf-gcc这样的 [[GCC_Cross-Compiler|交叉编译器]],那么你会得到一个响应,比如 “i686-elf”,这意味着编译器知道它在做别的事情,你可以轻松正确地避免很多问题。


== 如何构建交叉编译器 ==
==如何构建交叉编译器==
{{Main|GCC Cross Compiler}}
{{Main|GCC Cross Compiler}}


针对您的操作系统的[[GCC_Cross-Compiler | build a Cross-Compiler]]非常简单,而且需要一些时间。 在速度较慢的计算机上构建它可能需要一段时间,但您只需要做一次,并且您可以节省所有时间,否则您将花费在“修复”您将遇到的完全不必要的问题上。 稍后,当您开始为操作系统构建用户空间时,值得创建[[OS_-Specific_-Toolchain | OS-Specific Toolchain]]以绝对控制编译器并轻松编译用户空间程序。
这很容易,需要花一些时间来 [[GCC_Cross-Compiler|构建一个针对你的操作系统的交叉编译器]]。 在速度较慢的计算机上构建它可能需要一段时间,但你只需要做一次,你就可以节省所有的时间,否则你的时间就会花在“修复”你会遇到的完全不必要的问题上。 稍后,当你开始为操作系统构建用户空间时,有必要创建一个[[OS_SPECIAL_TOOLCHAIN|操作系统特定工具链]]来绝对控制编译器并轻松编译用户空间程序。


== 过渡到交叉编译器 ==
== 过渡到交叉编译器 ==
也许到目前为止您还没有使用[[GCC|u交叉编译器|交叉编译器]],在这种情况下,您可能会做很多错误的事情。 不幸的是,许多内核教程建议传递某些选项,并以一种可能导致很多麻烦的方式进行操作。 本节记录了您应该注意的一些事项。 请仔细阅读本节,如果您看到其他人使用了麻烦的选项,请向他们指出。
也许直到现在你还没有使用[[GCC_Cross-Compiler|交叉编译器]],在这种情况下,你可能会做很多错误的事情。 不幸的是,许多内核教程建议针对传统编译器传递某些选项,回避这些问题,并以可能会造成很多麻烦的方式进行操作。 本节记录了一些你应该注意的事情。 请仔细阅读本节,如果你看到其他人使用了麻烦的选项,请向他们指出。


=== 与编译器而不是ld链接 ===
=== 链接到你的编译器而不是ld ===
你不应该直接调用ld。 您的交叉编译器可以作为链接器工作,并使用它作为链接器,以便在链接阶段进行控制。 此控件包括将<tt>-lgcc</tt>扩展到只有编译器知道的[[libgcc]]的完整路径。 如果在编译过程中出现奇怪的错误,请使用交叉编译器进行链接,它可能会消失。 如果确实需要ld,请确保使用交叉链接器(i686 elf ld)而不是系统链接器。
你不应该直接调用ld。 你的交叉编译器可以作为链接器工作,并使用它作为链接器,以便在链接阶段进行控制。 这些控制包括将<tt>-lgcc</tt>展开为只有编译器知道的[[libgcc]]的完整路径。 如果在编译过程中出现奇怪的错误,请使用交叉编译器进行链接,它可能会消失。 如果确实需要ld,请确保使用交叉链接器(i686 elf ld)而不是系统链接器。


=== 使用交叉工具 ===
=== 使用交叉工具 ===
当您构建交叉binutil时,会得到很多有用的程序。 例如,您可以获得i686 elf readelf、i686 elf as、i686 elf objdump、i686 elf objcopy等等。 这些程序了解您的操作系统并正确处理所有问题。 如果您的本地操作系统附带的一些程序(readelf、objcopy、objdump)知道您的操作系统的文件格式,您可以使用它们,但通常最好使用跨工具。 如果操作系统的平台是i686 elf,则这些工具都始终具有前缀“i686 elf-”。
当你构建你的交叉binutils时,你会得到很多有用的程序。 例如,你可以获得i686 elf readelf、i686 elf as、i686 elf objdump、i686 elf objcopy等等。 这些程序了解你的操作系统,并正确处理所有事情。 你可以使用本地操作系统附带的一些程序 (readelf,objcopy,objdump),如果他们知道你的操作系统的文件格式, 但一般来说,最好使用交叉工具。 如果你的操作系统平台是i686-ELF,这些工具的前缀都是“i686-ELF-”。


=== 应该传递给编译器的选项 ===
=== 你应该使用的编译器选项 ===
您需要将一些特殊选项传递给编译器,以告知它没有构建用户空间程序。
你需要向编译器传递一些特殊选项,告诉它并没有在构建用户空间程序。


==== -ffreestanding ====
==== -ffreestanding ====
这很重要,因为它让编译器知道它正在构建内核而不是用户空间程序。 GCC的文档中说,您需要在独立模式下自行实现memset、memcpy、memcmp和memmove功能。
这很重要,因为它可以让编译器知道它正在构建内核而不是用户空间程序。 GCC的文档中说,你需要在独立模式(freestanding mode)下自行实现memset、memcpy、memcmp和memmove功能。


==== -mno-red-zone (x86_64 only) ====
==== -mno-red-zone(仅x86_64) ====
您需要在x86_64上传递此消息,否则中断将损坏堆栈。 红色区域是x86_64 ABI特性,这意味着信号发生在堆栈下128字节的位置。 允许使用少于该内存量的函数不增加堆栈指针。 这意味着内核中的CPU中断将损坏堆栈。 请确保对所有x86_64内核代码都通过启用。
你需要在x86_64上传递这个参数,否则中断会损坏堆栈。 由于red zone是x86_64 ABI特性,这意味着信号发生在堆栈下128字节的位置时会发生问题。 允许使用少于该内存量的函数不递增堆栈指针。 这会导致内核中的CPU中断将损坏堆栈。 请确保对所有x86_64内核代码启用此功能。


==== -fno-exceptions, -fno-rtti (C++) ====
==== -fno-exceptions, -fno-rtti (C++) ====
禁用内核中不工作的C++特性是明智的。 你需要向内核提供一个C++支持库(除了LIGBCC),以使所有C++特性都能工作。 如果不使用这些C++特性,则应该足以传递这些选项。
明智的做法是禁用在内核中无法开箱即用的C功能。 你需要向内核提供一个C++支持库(除了LIGBCC),以使所有C++特性都能工作。 如果你不使用这些C++特性,通过这些选项应该就足够了。


=== 您应该链接到的选项 ===
=== 你应该使用链接的选项 ===
这些选项只有在链接(而不是编译)时才有意义,您应该使用它们。 链接时还应传递编译选项,因为某些编译选项(如<tt>-mno red zone</tt>控制ABI,这也需要在链接时知道)。
这些选项只有在链接(而不是编译)时才有意义,你应该使用它们。 在链接时也应该传递编译选项,因为有些编译选项(比如<tt>-mno-red-zone</tt>控制ABI,这在链接时也需要知道)。


==== -nostdlib (same as both -nostartfiles -nodefaultlibs) ====
==== -nostdlib (-nostartfiles -nodefaultlibs 两者相同) ====
The -nostdlib option is the same as passing both the -nostartfiles -nodefaultlibs options. 您不希望启动文件(crt0.o、crti.o、crtn.o)位于内核中,因为它们只用于用户空间程序。 您不需要libc之类的默认库,因为用户空间版本不适合内核使用。 您应该只传递-nostlib,因为它与传递后两个选项相同。
-nostdlib选项与传递-nostartfiles-nodefaultlibs选项相同。 你不希望内核中包含启动文件(crt0.o、crti.o、crtn.o),因为它们只用于用户空间程序。 你不需要默认的库,例如libc,因为用户空间版本不适合内核使用。 你应该只需要-nostlib选项参数,因为它与同时使用后两个参数选项功能相同。


==== -lgcc ====
==== -lgcc ====
You disable the important [[libgcc]] library when you pass -nodefaultlibs (implied by -nostdlib). The compiler needs this library for many operations that it cannot do itself or that is more efficient to put into a shared function. 在所有其他对象文件和库之后,必须在链接行的末尾传递此库,否则链接器将不使用它,并且会出现奇怪的链接器错误。 这是由于经典的静态链接模型造成的,在该模型中,只有在以前的对象文件使用静态库中的对象文件时,才会将其拉入。 与[[libgcc]]的链接必须位于所有可能使用它的对象文件之后。
当你传递选项-nodefaultlibs (由-nostdlib隐含) 时,你禁用了重要的 [[libgcc]] 库。 编译器需要这个库来完成许多自己无法完成的操作,或者更有效地将这些操作放入共享函数中。 你必须在所有其它目标文件和库之后,在链接行的末尾传递此库,否则链接器不会使用它,你会得到奇怪的链接器错误。 这是由于经典的静态链接模式下,其中静态库中的目标文件仅在被先前的目标文件使用时才被拉入。 与[[libgcc]]的链接必须位于所有可能使用它的目标文件之后。


==不应传递给编译器的选项===
=== 不应传递给编译器的选项 ===
在构建内核时,有许多选项通常不应该传递给交叉编译器。 不幸的是,许多内核教程建议您使用这些。 请不要在不理解为什么需要某个选项的情况下传递该选项,也不要建议用户使用该选项。 通常,这些选项被那些不使用交叉编译器的人用来掩盖其他问题。
在构建内核时,你通常不应该将许多选项传递给交叉编译器。 不幸的是,很多内核教程建议你使用这些。 请不要在不了解为什么需要的情况下使用大量编译器选项,也不要建议其他人使用它们。 通常,这些选项被那些不使用交叉编译器的人,拿来掩盖其它问题。


==== -m32, -m64 (compiler) ====
==== -m32,-m64(编译器)====
If you build a cross-compiler such as i686-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 them into passing -m32 as well.
如果你构建了像i686-ELF-GCC这样的交叉编译器,那么你不需要告诉它生成32位可执行文件。 同样,你不需要将-m64选项提供给x86_64-elf-gcc。 这将使makefile变得更简单,因为你只需选择正确的编译器,一切都会正常工作。 你可以使用x86_64-ELF-GCC构建32位内核,但是单独构建两个交叉编译器并分别使用它们要容易得多。 此外,为目标的每个CPU使用交叉编译器将使移植第三方软件变得容易,而无需通过-m32选项欺骗它们。


==== -melf_i386, -melf_x86_64 (linker) ====
==== -melf_i386, -melf_x86_64 (链接器) ====
您不需要因为与-m32和-m64相同的原因通过这些。此外,这些选项是针对ld的,您首先不应该直接调用ld,而应该与交叉编译器链接。
由于上面与-m32和-m64相同的原因,你不需要使用这些参数。 此外,这些选项适用于ld,你不应该首先直接调用ld,而应该与交叉编译器链接。


==== -32, -64 (assembler) ====
==== -32,-64(汇编程序)====
交叉汇编程序(i686 elf as)默认为您在构建binutils时指定的平台,因此您无需在此处重复选择。 您可以将交叉编译器用作汇编程序,但直接调用汇编程序是可以的。
交叉汇编程序(i686-elf-as)默认使用你在构建binutil时指定的平台,因此你不需要在这里重复选择。 你可以将交叉编译器用作汇编器,但是直接调用汇编器也是可以的。


==== -nostdinc ====
==== -nostdinc ====
您不应该传递此选项,因为它会禁用标准标头包含目录。但是,您确实希望使用这些头,因为它们包含许多有用的声明。交叉编译器附带了一系列有用的头文件,如stddef。h、 斯特丁。h、 斯塔格。h、 还有更多。
你不应该传递此选项,因为它会禁用标准头包含目录。 但是,你会确实希望使用这些标头,因为它们包含许多有用的声明。 交叉编译器附带了一系列有用的头文件,比如stddef.h、 stdint.h、 stdarg.h、 还有其它更多头文件。


如果不使用交叉编译器,则会得到不适合操作系统的主机平台(如Linux)的标题。出于这个原因,大多数不使用交叉编译器的人使用这个选项,然后必须重新实现stddef。h、 斯特丁。h、 斯塔格。h和更多的人自己。人们经常错误地实现这些文件,因为您需要编译器魔法来实现诸如stdarg之类的功能。H
如果你不使用交叉编译器,你将获得不适合你的操作系统的主机平台(如Linux)的头文件。 因此,大多数不使用交叉编译器的人都使用此选项,然后必须自己重新实现stddef.h,stdint.h,stdarg.h等等头文件。 人们经常错误地实现这些文件,实现stdarg.h等功能还是需要一些编译器深入理解的


==== -fno-builtin ====
==== -fno-builtin ====
此选项由<tt>-f重新理解<tt>表示,您没有理由自己通过此选项。 编译器默认为-fbuiltin以启用内置,但-fno builtin将禁用它们。 内置意味着编译器了解标准特性并可以优化它们的使用。 如果编译器看到一个名为“strlen”的函数,它通常会假定它是C标准的“strlen”函数,并且能够在编译时将表达式strlen(“foo”)优化为3,而不是调用该函数。 如果您正在创建一些非常非标准的环境,其中常见的C函数没有它们通常的语义,那么这个选项很有价值。 在<tt>-f重新理解之后,可以使用<tt>-fbuiltin</tt>再次启用内置,但这可能会导致令人惊讶的问题,例如,将calloc(malloc+memset)的实现优化为调用calloc本身。
<tt>-ffreestanding</tt> 隐含了此选项,没有理由自己设置该选项。 编译器默认为-fbuiltin以启用内置函数,但-fno-builtin将禁用它们。 内置意味着编译器了解标准功能并可以优化它们的使用。 如果编译器看到一个名为 “strlen” 的函数,则通常假定它是C标准 “strlen” 函数,并且能够在编译时将表达式strlen (“foo”) 直接优化为3,而不是调用该函数。 如果你正在创建一些非常非标准的环境,其中常见的C函数没有它们通常的语义,那么这个选项很有价值。 可以在<tt>-ffreestanding</tt>之后使用<tt>-fbuiltin</tt>再次启用内置,但这可能会导致以后出现令人惊讶的问题,比如calloc(malloc+memset)的实现被优化为对calloc本身的调用。


==== -fno-stack-protector ====
==== -fno-stack-protector ====
The [[Stack Smashing Protector]] is a feature that stores a random value on the stack of selected functions and verifies the value is intact upon return. 这在统计上防止堆栈缓冲区溢出覆盖堆栈上的返回指针,从而破坏控制流。 对手通常能够利用此类故障,此功能要求对手正确猜测32位值(32位系统)或64位值(64位系统)。此安全功能需要[[Stack Smashing Protector |运行时支持]]。 许多操作系统供应商的编译器通过将<tt>-fstack-protector</tt>作为默认值来启用此功能。 这会破坏不使用交叉编译器的内核,如果它们没有运行时支持的话。 交叉编译器(如<tt>*-elf</tt>目标)在默认情况下禁用了堆栈保护器,您没有理由自己禁用它。 当您向内核(和用户空间)添加对它的支持时,您可能希望将默认设置更改为启用它,这将使内核自动使用它,因为您没有通过此选项。
[[Stack Smashing Protector]]是一种功能,它在所选函数的堆栈上存储一个随机值,并在返回时验证该值是否完整。 这在统计上可以防止堆栈缓冲区溢出覆盖堆栈上的返回指针,从而破坏控制流。 此功能要求攻击者能正确猜测一个32位值 (32位系统) 或64位值 (64位系统),使攻击者无法利用此类故障,。 此安全功能需要[[Stack Smashing Protector|运行时支持]]。 来自许多操作系统供应商的编译器通过将<tt>-fstack-protector</tt>设为默认设置来启用此功能。 如果没有运行时支持,攻击者就会能破坏不使用交叉编译器的内核。 交叉编译器(如<tt>*-elf</tt>目标)在默认情况下禁用了堆栈保护器,最好不要自己再显式禁用它。 因为当你向内核(和用户空间)添加对栈保护的支持时,你可能希望再将缺省值更改为启用它,这将使内核能自动使用它。


== 没有交叉编译器时出现的问题 ==
== 在没有交叉编译器的情况下出现的问题 ==
要使用系统gcc构建内核,需要克服很多问题。 You don't need to deal with these problems if you use a cross-compiler.
要使用系统gcc构建内核,需要克服很多问题。 如果使用交叉编译器,则不需要处理这些问题。


=== 更复杂的编译命令 ===
=== 更复杂的编译命令 ===
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:
编译器假定它是针对本地系统的,因此需要很多选项才能使其正常工作。 用于在没有交叉编译器的情况下编译内核的精简命令序列可能如下所示:


<source lang="bash">
<source lang="bash">
第82行: 第82行:
</source>
</source>


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:
实际上,一般情况更糟。 人们倾向于添加更多有问题或多余的选项。 而使用真正的交叉编译器,命令序列可能如下所示:


<source lang="bash">
<source lang="bash">
第91行: 第91行:


=== 重新实现libgcc ===
=== 重新实现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.
构建内核时不能使用主机[[libgcc]]。 上次我检查过Linux[[libgcc]]有一些令人讨厌的依赖项。 比如常见一种情况,新手遇到在32位系统上进行64位整数除法,但是编译器在许多情况下可能会生成此类调用。 当你本来应该使用真实的东西时,你经常会重写[[libgcc]]


=== Rewriting freestanding headers (often incorrectly) ===
=== 重写freestanding头(通常不正确) ===
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.
如果你不通过-nostdinc你得到目标系统头 (这是你的本地系统,如果不使用交叉编译器),这将导致很多问题在非交叉编译器的情况下。 最终,你将重写标准的独立头,例如stdarg.h、 stddef.h、 stdint.h、 还有更多。 不幸的是,如上所述,这些头文件需要一点编译器魔力才能恰到好处。 如果你使用交叉编译器,那么所有这些独立的标头都可以开箱即用。


=== 复杂的用户空间程序编译 ===
=== 复杂的用户空间程序编译 ===
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
你需要将更多选项传递给为你的操作系统构建程序的命令行。 你需要一个-Ipath/ /myos/include -Lpath/ /myos/lib来使用C库,以及更多。 如果你建立了一个[[OS Specific Toolchain]],你只需要


<source lang="bash">
<source lang="bash">
第103行: 第103行:
</source>
</source>


to cross-compile the hello world program to your operating system.
将hello world程序交叉编译到你的操作系统。


=== Compiler releases break your OS ===
=== 不同编译器发布版破坏你的操作系统 ===
Not everyone is using the same gcc as you 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.
当你的自制系统代码发布后,不是每一个人都在和你使用一样的gcc,意味着在其它操作系统(甚至是版本或编译器发行版不同)上的人要正确构建你的操作系统,可能会遇到麻烦。 如果你使用交叉编译器,那么每个人都在使用相同的编译器版本,并且关于主机系统的环境假设不会进入你的操作系统。


=== 支持 ===
=== 支持 ===
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.
你将更容易获得操作系统开发社区的支持。 正确使用交叉编译器表明你遵循了说明,与其它所有人处于同一技术层,并且你的本地系统编译器没有造成麻烦。


=== 等等 ===
===等等===
随着项目规模的增长,在没有真正的交叉编译器的情况下维护操作系统变得更加复杂。 即使你的ABI很像Linux,你的操作系统也不是Linux。 如果没有交叉编译器,移植第三方软件几乎是不可能的。 如果您设置了真正的[[OS-Specific Toolchain]][[sysroot]]操作系统,只需将--host=i686 myos提供给即可编译软件/配置。 使用交叉编译器,您可以以标准方式[[交叉移植软件|端口软件]]。
随着项目规模的增长,在没有真正的交叉编译器的情况下维护操作系统变得更加复杂。 即使你的ABI很像Linux,你的操作系统也不是Linux。 如果没有交叉编译器,移植第三方软件几乎是不可能的。 如果你设置了一个真正的 [[OS Specific Toolchain|操作系统特定的工具链]] 和操作系统的 [[sysroot]],则只需将-host=i686-myos提供给./configure即可编译软件。 使用交叉编译器,你可以以标准方式[[Cross-Porting Software|移植软件]]。


== 背景资料 ==
==背景信息==


=== 交叉编译的想法从何而来?===
=== 交叉编译的想法从何而来?===


对于GNU软件,由于大部分都有从一个UNIX实现移植到另一个UNIX实现的悠久历史,他们提出了一组简单的规则:您有一台构建机、一台主机和一台目标机。
对于GNU软件,由于大部分都有从一个UNIX实现移植到另一个UNIX实现的悠久历史,他们提出了一套简单的规则: 你有一台构建机、一台主机和一台目标机。


=== 交叉编译的基础是什么? ===
=== 交叉编译的基础是什么?===


“构建”机器就是您在其上编译软件的机器。 正在编译的软件可能被编译为在其他类型的机器上运行。 请看,您可能正在基于x86的机器上构建,并希望该软件在基于SPARC的机器上运行。 生成机器是隐式的,通常由软件的配置脚本自动检测。 它唯一的真正用途是,如果正在编译的软件选择将用于配置它的configure参数保留在构建包中的某个位置,那么向其分发包的人将知道包构建在哪台机器上。 如果构建机在构建该软件时存在某些问题,则可以使用构建机的名称将包配置为使用变通方法。
“构建”机就是编译软件的机器。 正在编译的该软件可以编译为在其它类型的计算机上运行。 例如,你可能正在基于x86的机器上构建,并希望该软件在基于SPARC的机器上运行。 构建机是隐式的,通常会由软件的配置脚本自动检测。 它唯一真正的目的是,如果正在编译的软件选择将用于配置它的configure参数保留在构建的软件包中的某个位置,则分发该软件包的人将知道该软件包是在什么机器上构建的。 如果构建机在构建该软件时存在某些问题,则可以使用构建机的名称将包配置为使用变通方法。


“主机”机器是软件必须运行的机器。 因此,在前面的示例中,“构建”机器是一台i686 elf yourBuildOs机器,主机是一台sparc32-elf-unix4机器。
“主机”是软件必须在其上运行的机器。因此,在前面的示例中,“构建”机是一台i686-elf-yourBuildOS机器,主机是一台sparc32-elf-unix4。


在本例中,您必须有一个sparc32-elf-unix4交叉编译器,它可以在i686 elf yourBuildOs机器上运行,并吐出(目标)主机。 交叉编译器通常以其目标主机命名,而不是以其运行的主机命名,因此通过查看编译器的名称,通常可以知道它目标的机器。
在这个例子中,你必须有一个sparc32-elf-unix4交叉编译器,它可以在i686-elf-yourBuildOs的机器上运行,并编译出以你的主机为目标的结果。 交叉编译器通常以其目标主机命名,而不是以其运行的主机命名,因此通过查看编译器的名称,通常可以知道它以哪台机器为目标。


“目标”仅在编译用于构建其他软件的软件时起作用。 也就是说,在编译编译器、链接器、汇编程序等时。 他们需要被告知自己将瞄准哪台机器。 当编译电影播放器或其他非建筑软件时,“目标”并不重要。 换言之,“target”用于告诉编译器或其他正在编译的开发软件它的目标主机,即正在编译的编译器。
“目标”只在编译用于构建其它软件的软件时才重要。 即在编译获得编译器、链接器、汇编程序等时。 他们需要被告知自己将针对哪类机器。 当编译视频播放器或其它非构建类软件时,“目标”并不重要。 换句话说,“目标” 用于告诉正在编译的编译器或其它开发软件,即正在编译的编译器应该以什么为目标。


对于大多数软件,如文本编辑器,如果您正在编译它,您只需要指定主机(通常甚至不需要指定主机)。指定主机会导致使用生成计算机上的编译器生成软件,该编译器是针对该主机的交叉编译器。 这样,由于该软件是使用该主机的目标交叉编译器编译的,因此该软件将能够在该主机上运行,即使它构建在(可能)不同的构建机器上。
对于大多数软件,比如文本编辑器,如果你正在编译它,你只需要指定主机(通常甚至不需要指定主机)。 指定主机会导致使用构建机上针对该主机的交叉编译器构建软件。 这样,由于该软件是使用该主机的目标交叉编译器编译的,因此该软件将能够在该主机上运行,即使它是在 (可能) 不同的计算机上构建的。


=== 具体事物的例子 ===
=== 一个具体事物的例子 ===


您的发行版配备了一个编译器,该编译器以您的MachineSearch distributionNativeExecFormat发行版为目标,并在同一台您的MachineSearch distributionNativeExecFormat发行版计算机上运行。 它是在某些机器上构建的(可能是在m68k macho osx或其他类似的彩色机器上构建的),带有一个针对您的MachineSearch distributionNativeExecFormat发行版的交叉编译器,在构建过程中,它自己的“目标”设置为您的MachineSearch distributionNativeExecFormat发行版,因此,它现在既可以运行,也可以为您的MachineSearch distributionNativeExecFormat分发吐出可执行文件。
你的发行版附带了一个面向yourMachineArch-distributionNativeExecFormat-distribution的编译器 并且在一台yourMachineArch-distributionNativeExecFormat-distribution机器上运行。 它是在其它机器上构建的(可能是在m68k-macho-osx或其它类似的好用机器上构建的),带有一个针对yourMachineArch-distributionNativeExecFormat-distribution的交叉编译器,在构建过程中,它自己的“目标”设置为yourMachineArch-distributionNativeExecFormat-distribution, 因此它现在既可以在上运行,又可以输出yourMachineArch-distributionNativeExecFormat-distribution的可执行文件


交叉编译器对于完全明确地针对特定机器非常有用。 当您开始为独立于本机发行版的股票编译器的机器开发时,您将需要一个交叉编译器。 实际上,从您第一次开始尝试生成完全独立的可执行文件(即,当您在进行OSDev时)开始,您就已经*确实*针对一台与本机不同的机器。
交叉编译器对于完全明确地针对特定机器很有用。 当你开始为一台独立于本机发行版的股票编译器的机器开发时,你将需要一个交叉编译器。 实际上,从你第一次尝试生成完全独立的可执行文件(即,当你在做OSDev时)起,你就已经开始尝试建立了一台与你的本机不同的机器。


本机编译器以MachineSearch分发版NativeExecFormat分发版为目标。 但是你想以你的目标为目标搜索你选择的格式无;其中,“none”作为操作系统名称表示没有系统库的机器。 编译内核时,您希望使用不知道目标机器的任何系统库的编译器进行编译。 内核必须完全独立。 内核所需的一切都必须在其源代码树中,这样就不需要隐式系统库(或者在您不知情的情况下链接)。
你的本机编译器针对yourMachineArch-distributionNativeExecFormat-distribution。 但是你想把yourOsTargetArch-yourChosenFormat-none定为目标;其中,“none”作为操作系统名称表示没有系统库的机器。 编译内核时,你希望使用不知道目标计算机的任何系统库的编译器进行编译。 内核必须完全独立。 内核所需的一切都必须在其源代码树中,这样就不需要隐式的系统库(或者在你不知情的情况下链接进来)。


稍后,当您有一个用户空间和一组系统库供您的程序链接时,您将构建一个针对以下对象的编译器:yourOsTargetArch yourChosenFormat yourOs,同时保留针对yourOsTargetArch yourChosenFormat none的编译器。 两者之间的区别在于,“无”操作系统目标编译器在其/lib目录中没有任何库,也没有可链接的系统库。 因此,使用这种“裸机”目标编译器编译的任何软件都不可能具有未知的依赖项。 以交叉编译器为目标的“yourOS”是您放置自己的系统库(在其/lib目录中)的地方,这样,当它构建软件时,它会将它们与这些系统库链接,从而将它们与内核API链接。 您将继续使用“基本”目标编译器来构建内核,并且每当您在开发机器上构建预期在内核目标机器上运行的程序时,您都将使用yourOsTargetArch yourChosenFormat yourOs编译器,它有一个/include和/lib目录,其中充满了您自己操作系统的本机系统包含和库。
稍后,当你有了一个用户空间和一组可供程序链接的系统库时,你将构建一个目标为:yourOsTargetArch-yourChosenFormat-yourOs的编译器,同时保留面向yourOsTargetArch-yourChosenFormat-none的编译器。 两者之间的区别在于,“none” OS目标编译器在其/lib目录中没有任何库,并且没有可链接的系统库。 因此,使用这种“裸机”目标编译器编译的任何软件都不可能有未知的依赖项。 以“yourOS”为目标的交叉编译器是将你自己的系统库(在其/lib目录中)放在其中的地方,以便在构建软件时,它会将它们链接到那些系统库中,从而将它们链接到你的内核API中。 你将继续使用 “bare bones” 目标编译器来构建内核, 无论何时,只要你在开发机器上构建程序,并希望在内核的目标机器上运行, 你可以使用yourOsTargetArch-yourChosenFormat-yourOs编译器,它有一个/include和/lib目录,其中充满了你自己操作系统的本机系统包含和库。


稍后,当您确信可以从内核内部开发和使用内核时,您将希望停止在单独的“开发”机器上构建所有程序。 更具体地说,在这一点上,您希望您的“构建”机器与您的测试机器相同,这样您就可以从操作系统内部构建在操作系统上运行的程序。 此时,是采取最后一步的时候了,从您单独的“开发”机器上,构建一个将在您的操作系统上运行的编译器(host=yourOsTargetArch yourChosenFormat yourOs),并以您的操作系统为目标(target=yourOsTargetArch yourChosenFormat yourOs)。这个交叉编译器,即加拿大交叉编译器,允许您在自己的操作系统用户空间上运行时以本机方式编译其他程序。 它本质上是一个“发行版原生编译器”,就像发行版附带的编译器一样。 从那时起,如果有一个编译器运行在你的操作系统上并以你的操作系统为目标,你就可以更自由地移植程序,并允许人们在运行你的操作系统时做同样的事情,假设你用你的操作系统打包了这个本机编译器。
稍后,当你确信可以从内核中开发和使用内核时,你将希望停止在单独的 “开发” 机器上构建所有程序。 更具体地说,在这一点上,你希望你的“构建”机器与测试机器相同,这样你就可以从操作系统内部构建在操作系统上运行的程序。 此时,是执行最后一步的时候了,在你单独的“开发”机器上,构建一个将在你的操作系统(host = yourOsTargetArch-yourChosenFormat-yourOs)上运行的编译器,并针对你的操作系统(target=yourOsTargetArch-yourChosenFormat-yourOs)。 这个交叉编译器,即[[GCC_Canadian_Cross]],将允许你在自己的操作系统的用户空间上运行时本地编译其它程序。 它本质上是一个“发行版原生编译器”,就像发行版附带的编译器一样。 从这时起,就有了一个在你的操作系统上运行并面向你的操作系统的编译器,你基本上可以更自由地移植程序,并允许人们在运行你的操作系统时执行同样的操作(假设你将本地编译器与你的操作系统一起打包)。


=== 我什么时候不需要交叉编译器? ===
=== 我什么时候不需要交叉编译器?===
如果您创建了一个真正的操作系统,并设法将gcc移植到它,那么gcc将生成与i686 myos gcc完全相同的代码。 这意味着您不需要在自己的操作系统上使用交叉编译器,因为那里的gcc已经做了正确的事情。 This is why the Linux kernel is built with the Linux gcc, instead of a Linux cross-compiler.
如果你创建了一个真正的操作系统,并设法将gcc移植到它,那么gcc将生成与i686-myos-gcc完全相同的代码。 这意味着你不需要在你自己的操作系统上进行交叉编译,因为那里的GCC已经在做正确的事情了。 这就是linux内核使用Linux gcc而不是Linux交叉编译器构建的原因。


=== 一个具体的例子 ===
=== 一个具体的例子 ===


*gcc -v* from my Ubuntu machine gives:
* 我的Ubuntu机器上的gcc -v* 使用结果:


<pre>
<pre>
第166行: 第166行:
</pre>
</pre>


The person who built the compiler that runs on my machine built it on a machine just like mine, an 'i486-linux-gnu' (build), and intended for it to run on a machine like his/mine: the same i486-linux-gnu (host), and he meant for this compiler he was building for me, to emit executables that targeted my very machine, so that when I compile programs, they will be able to run on a host that is i486-linux-gnu. Therefore he made the compiler target i486-linux-gnu.
有人构建了在我的机器上运行的编译器,他在和我一样的机器上构建了它,一台‘i486-linux-gnu’(构建机),并且打算让它在像他/我这样的机器上运行: 同样是i486-linux-gnu(主机),他的意思是这个编译器是他为我构建,发出针对我的机器的可执行文件,这样当我编译程序时,结果程序将能够在i486-linux-gnu主机上运行。 因此,他使编译器目标平台为i486-linux-gnu。


== See Also ==
==另见==


=== Articles ===
===文章===
* [[GCC Cross-Compiler]]
* [[GCC Cross-Compiler|GCC交叉编译器]]
* [[Cross-Porting Software]]
* [[Cross-Porting Software|交叉移植软件]]


[[Category:FAQ]]
[[Category:FAQ]]
[[Category:OS_Development]]
[[Category:OS_Development]]
[[Category:OS_theory]]
[[Category:OS_theory]]

2022年3月13日 (日) 08:31的最新版本

注意: 本页面是特定于GCC的。 如果你使用另一个编译器,你应该研究一下该编译器通常是如何进行交叉编译的,并且应该这样做。 GCC与其本机目标系统紧密绑定,许多其它编译器则没有。 有些编译器甚至没有本机目标,它们始终是交叉编译器。

‘’除非‘’你是在自己的操作系统上开发的,你需要使用交叉编译器。 编译器「必须」知道正确的 目标平台 (CPU,操作系统),否则会遇到麻烦。 如果你通过一系列选项来配置编译系统,你也许可以使用系统附带的编译器,但这会产生很多完全不必要的问题。

可以通过调用以下命令来询问编译器当前使用的目标平台是什么:

 gcc -dumpmachine

如果你在64位Linux上进行开发,那么你将获得诸如 “x86_64-unknown-linux-gnu” 之类的响应。 这意味着编译器认为它正在为Linux创建代码。 如果你使用这个GCC来构建你的内核,它将使用你的系统库、头文件、LINUX libgcc,并且它会做出很多有问题的LINUX环境假设。 如果你使用像i686-elf-gcc这样的 交叉编译器,那么你会得到一个响应,比如 “i686-elf”,这意味着编译器知道它在做别的事情,你可以轻松正确地避免很多问题。

如何构建交叉编译器

正文: GCC Cross Compiler

这很容易,需要花一些时间来 构建一个针对你的操作系统的交叉编译器。 在速度较慢的计算机上构建它可能需要一段时间,但你只需要做一次,你就可以节省所有的时间,否则你的时间就会花在“修复”你会遇到的完全不必要的问题上。 稍后,当你开始为操作系统构建用户空间时,有必要创建一个操作系统特定工具链来绝对控制编译器并轻松编译用户空间程序。

过渡到交叉编译器

也许直到现在你还没有使用交叉编译器,在这种情况下,你可能会做很多错误的事情。 不幸的是,许多内核教程建议针对传统编译器传递某些选项,回避这些问题,并以可能会造成很多麻烦的方式进行操作。 本节记录了一些你应该注意的事情。 请仔细阅读本节,如果你看到其他人使用了麻烦的选项,请向他们指出。

链接到你的编译器而不是ld

你不应该直接调用ld。 你的交叉编译器可以作为链接器工作,并使用它作为链接器,以便在链接阶段进行控制。 这些控制包括将-lgcc展开为只有编译器知道的libgcc的完整路径。 如果在编译过程中出现奇怪的错误,请使用交叉编译器进行链接,它可能会消失。 如果确实需要ld,请确保使用交叉链接器(i686 elf ld)而不是系统链接器。

使用交叉工具

当你构建你的交叉binutils时,你会得到很多有用的程序。 例如,你可以获得i686 elf readelf、i686 elf as、i686 elf objdump、i686 elf objcopy等等。 这些程序了解你的操作系统,并正确处理所有事情。 你可以使用本地操作系统附带的一些程序 (readelf,objcopy,objdump),如果他们知道你的操作系统的文件格式, 但一般来说,最好使用交叉工具。 如果你的操作系统平台是i686-ELF,这些工具的前缀都是“i686-ELF-”。

你应该使用的编译器选项

你需要向编译器传递一些特殊选项,告诉它并没有在构建用户空间程序。

-ffreestanding

这很重要,因为它可以让编译器知道它正在构建内核而不是用户空间程序。 GCC的文档中说,你需要在独立模式(freestanding mode)下自行实现memset、memcpy、memcmp和memmove功能。

-mno-red-zone(仅x86_64)

你需要在x86_64上传递这个参数,否则中断会损坏堆栈。 由于red zone是x86_64 ABI特性,这意味着信号发生在堆栈下128字节的位置时会发生问题。 允许使用少于该内存量的函数不递增堆栈指针。 这会导致内核中的CPU中断将损坏堆栈。 请确保对所有x86_64内核代码启用此功能。

-fno-exceptions, -fno-rtti (C++)

明智的做法是禁用在内核中无法开箱即用的C功能。 你需要向内核提供一个C++支持库(除了LIGBCC),以使所有C++特性都能工作。 如果你不使用这些C++特性,通过这些选项应该就足够了。

你应该使用链接的选项

这些选项只有在链接(而不是编译)时才有意义,你应该使用它们。 在链接时也应该传递编译选项,因为有些编译选项(比如-mno-red-zone控制ABI,这在链接时也需要知道)。

-nostdlib (与-nostartfiles -nodefaultlibs 两者相同)

-nostdlib选项与传递-nostartfiles-nodefaultlibs选项相同。 你不希望内核中包含启动文件(crt0.o、crti.o、crtn.o),因为它们只用于用户空间程序。 你不需要默认的库,例如libc,因为用户空间版本不适合内核使用。 你应该只需要-nostlib选项参数,因为它与同时使用后两个参数选项功能相同。

-lgcc

当你传递选项-nodefaultlibs (由-nostdlib隐含) 时,你禁用了重要的 libgcc 库。 编译器需要这个库来完成许多自己无法完成的操作,或者更有效地将这些操作放入共享函数中。 你必须在所有其它目标文件和库之后,在链接行的末尾传递此库,否则链接器不会使用它,你会得到奇怪的链接器错误。 这是由于经典的静态链接模式下,其中静态库中的目标文件仅在被先前的目标文件使用时才被拉入。 与libgcc的链接必须位于所有可能使用它的目标文件之后。

不应传递给编译器的选项

在构建内核时,你通常不应该将许多选项传递给交叉编译器。 不幸的是,很多内核教程建议你使用这些。 请不要在不了解为什么需要的情况下使用大量编译器选项,也不要建议其他人使用它们。 通常,这些选项被那些不使用交叉编译器的人,拿来掩盖其它问题。

-m32,-m64(编译器)

如果你构建了像i686-ELF-GCC这样的交叉编译器,那么你不需要告诉它生成32位可执行文件。 同样,你不需要将-m64选项提供给x86_64-elf-gcc。 这将使makefile变得更简单,因为你只需选择正确的编译器,一切都会正常工作。 你可以使用x86_64-ELF-GCC构建32位内核,但是单独构建两个交叉编译器并分别使用它们要容易得多。 此外,为目标的每个CPU使用交叉编译器将使移植第三方软件变得容易,而无需通过-m32选项欺骗它们。

-melf_i386, -melf_x86_64 (链接器)

由于上面与-m32和-m64相同的原因,你不需要使用这些参数。 此外,这些选项适用于ld,你不应该首先直接调用ld,而应该与交叉编译器链接。

-32,-64(汇编程序)

交叉汇编程序(i686-elf-as)默认使用你在构建binutil时指定的平台,因此你不需要在这里重复选择。 你可以将交叉编译器用作汇编器,但是直接调用汇编器也是可以的。

-nostdinc

你不应该传递此选项,因为它会禁用标准头包含目录。 但是,你会确实希望使用这些标头,因为它们包含许多有用的声明。 交叉编译器附带了一系列有用的头文件,比如stddef.h、 stdint.h、 stdarg.h、 还有其它更多头文件。

如果你不使用交叉编译器,你将获得不适合你的操作系统的主机平台(如Linux)的头文件。 因此,大多数不使用交叉编译器的人都使用此选项,然后必须自己重新实现stddef.h,stdint.h,stdarg.h等等头文件。 人们经常错误地实现这些文件,实现stdarg.h等功能还是需要一些编译器深入理解的

-fno-builtin

-ffreestanding 隐含了此选项,没有理由自己设置该选项。 编译器默认为-fbuiltin以启用内置函数,但-fno-builtin将禁用它们。 内置意味着编译器了解标准功能并可以优化它们的使用。 如果编译器看到一个名为 “strlen” 的函数,则通常假定它是C标准 “strlen” 函数,并且能够在编译时将表达式strlen (“foo”) 直接优化为3,而不是调用该函数。 如果你正在创建一些非常非标准的环境,其中常见的C函数没有它们通常的语义,那么这个选项很有价值。 可以在-ffreestanding之后使用-fbuiltin再次启用内置,但这可能会导致以后出现令人惊讶的问题,比如calloc(malloc+memset)的实现被优化为对calloc本身的调用。

-fno-stack-protector

Stack Smashing Protector是一种功能,它在所选函数的堆栈上存储一个随机值,并在返回时验证该值是否完整。 这在统计上可以防止堆栈缓冲区溢出覆盖堆栈上的返回指针,从而破坏控制流。 此功能要求攻击者能正确猜测一个32位值 (32位系统) 或64位值 (64位系统),使攻击者无法利用此类故障,。 此安全功能需要运行时支持。 来自许多操作系统供应商的编译器通过将-fstack-protector设为默认设置来启用此功能。 如果没有运行时支持,攻击者就会能破坏不使用交叉编译器的内核。 交叉编译器(如*-elf目标)在默认情况下禁用了堆栈保护器,最好不要自己再显式禁用它。 因为当你向内核(和用户空间)添加对栈保护的支持时,你可能希望再将缺省值更改为启用它,这将使内核能自动使用它。

在没有交叉编译器的情况下出现的问题

要使用系统gcc构建内核,需要克服很多问题。 如果使用交叉编译器,则不需要处理这些问题。

更复杂的编译命令

编译器假定它是针对本地系统的,因此需要很多选项才能使其正常工作。 用于在没有交叉编译器的情况下编译内核的精简命令序列可能如下所示:

 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

实际上,一般情况更糟。 人们倾向于添加更多有问题或多余的选项。 而使用真正的交叉编译器,命令序列可能如下所示:

 i686-elf-as boot.s -o boot.o
 i686-elf-gcc kernel.c -o kernel.o -ffreestanding
 i686-elf-gcc -T link.ld boot.o kernel.o -o kernel.bin -nostdlib -ffreestanding -lgcc

重新实现libgcc

构建内核时不能使用主机libgcc。 上次我检查过Linuxlibgcc有一些令人讨厌的依赖项。 比如常见一种情况,新手遇到在32位系统上进行64位整数除法,但是编译器在许多情况下可能会生成此类调用。 当你本来应该使用真实的东西时,你经常会重写libgcc

重写freestanding头(通常不正确)

如果你不通过-nostdinc你得到目标系统头 (这是你的本地系统,如果不使用交叉编译器),这将导致很多问题在非交叉编译器的情况下。 最终,你将重写标准的独立头,例如stdarg.h、 stddef.h、 stdint.h、 还有更多。 不幸的是,如上所述,这些头文件需要一点编译器魔力才能恰到好处。 如果你使用交叉编译器,那么所有这些独立的标头都可以开箱即用。

复杂的用户空间程序编译

你需要将更多选项传递给为你的操作系统构建程序的命令行。 你需要一个-Ipath/ 到 /myos/include 和 -Lpath/ 到 /myos/lib来使用C库,以及更多。 如果你建立了一个OS Specific Toolchain,你只需要

 i686-myos-gcc hello.c -o hello

将hello world程序交叉编译到你的操作系统。

不同编译器发布版破坏你的操作系统

当你的自制系统代码发布后,不是每一个人都在和你使用一样的gcc,意味着在其它操作系统(甚至是版本或编译器发行版不同)上的人要正确构建你的操作系统,可能会遇到麻烦。 如果你使用交叉编译器,那么每个人都在使用相同的编译器版本,并且关于主机系统的环境假设不会进入你的操作系统。

支持

你将更容易获得操作系统开发社区的支持。 正确使用交叉编译器表明你遵循了说明,与其它所有人处于同一技术层,并且你的本地系统编译器没有造成麻烦。

等等

随着项目规模的增长,在没有真正的交叉编译器的情况下维护操作系统变得更加复杂。 即使你的ABI很像Linux,你的操作系统也不是Linux。 如果没有交叉编译器,移植第三方软件几乎是不可能的。 如果你设置了一个真正的 操作系统特定的工具链 和操作系统的 sysroot,则只需将-host=i686-myos提供给./configure即可编译软件。 使用交叉编译器,你可以以标准方式移植软件

背景信息

交叉编译的想法从何而来?

对于GNU软件,由于大部分都有从一个UNIX实现移植到另一个UNIX实现的悠久历史,他们提出了一套简单的规则: 你有一台构建机、一台主机和一台目标机。

交叉编译的基础是什么?

“构建”机就是编译软件的机器。 正在编译的该软件可以编译为在其它类型的计算机上运行。 例如,你可能正在基于x86的机器上构建,并希望该软件在基于SPARC的机器上运行。 构建机是隐式的,通常会由软件的配置脚本自动检测。 它唯一真正的目的是,如果正在编译的软件选择将用于配置它的configure参数保留在构建的软件包中的某个位置,则分发该软件包的人将知道该软件包是在什么机器上构建的。 如果构建机在构建该软件时存在某些问题,则可以使用构建机的名称将包配置为使用变通方法。

“主机”是软件必须在其上运行的机器。因此,在前面的示例中,“构建”机是一台i686-elf-yourBuildOS机器,主机是一台sparc32-elf-unix4。

在这个例子中,你必须有一个sparc32-elf-unix4交叉编译器,它可以在i686-elf-yourBuildOs的机器上运行,并编译出以你的主机为目标的结果。 交叉编译器通常以其目标主机命名,而不是以其运行的主机命名,因此通过查看编译器的名称,通常可以知道它以哪台机器为目标。

“目标”只在编译用于构建其它软件的软件时才重要。 即在编译获得编译器、链接器、汇编程序等时。 他们需要被告知自己将针对哪类机器。 当编译视频播放器或其它非构建类软件时,“目标”并不重要。 换句话说,“目标” 用于告诉正在编译的编译器或其它开发软件,即正在编译的编译器应该以什么为目标。

对于大多数软件,比如文本编辑器,如果你正在编译它,你只需要指定主机(通常甚至不需要指定主机)。 指定主机会导致使用构建机上针对该主机的交叉编译器构建软件。 这样,由于该软件是使用该主机的目标交叉编译器编译的,因此该软件将能够在该主机上运行,即使它是在 (可能) 不同的计算机上构建的。

一个具体事物的例子

你的发行版附带了一个面向yourMachineArch-distributionNativeExecFormat-distribution的编译器 并且在一台yourMachineArch-distributionNativeExecFormat-distribution机器上运行。 它是在其它机器上构建的(可能是在m68k-macho-osx或其它类似的好用机器上构建的),带有一个针对yourMachineArch-distributionNativeExecFormat-distribution的交叉编译器,在构建过程中,它自己的“目标”设置为yourMachineArch-distributionNativeExecFormat-distribution, 因此它现在既可以在上运行,又可以输出yourMachineArch-distributionNativeExecFormat-distribution的可执行文件

交叉编译器对于完全明确地针对特定机器很有用。 当你开始为一台独立于本机发行版的股票编译器的机器开发时,你将需要一个交叉编译器。 实际上,从你第一次尝试生成完全独立的可执行文件(即,当你在做OSDev时)起,你就已经开始尝试建立了一台与你的本机不同的机器。

你的本机编译器针对yourMachineArch-distributionNativeExecFormat-distribution。 但是你想把yourOsTargetArch-yourChosenFormat-none定为目标;其中,“none”作为操作系统名称表示没有系统库的机器。 编译内核时,你希望使用不知道目标计算机的任何系统库的编译器进行编译。 内核必须完全独立。 内核所需的一切都必须在其源代码树中,这样就不需要隐式的系统库(或者在你不知情的情况下链接进来)。

稍后,当你有了一个用户空间和一组可供程序链接的系统库时,你将构建一个目标为:yourOsTargetArch-yourChosenFormat-yourOs的编译器,同时保留面向yourOsTargetArch-yourChosenFormat-none的编译器。 两者之间的区别在于,“none” OS目标编译器在其/lib目录中没有任何库,并且没有可链接的系统库。 因此,使用这种“裸机”目标编译器编译的任何软件都不可能有未知的依赖项。 以“yourOS”为目标的交叉编译器是将你自己的系统库(在其/lib目录中)放在其中的地方,以便在构建软件时,它会将它们链接到那些系统库中,从而将它们链接到你的内核API中。 你将继续使用 “bare bones” 目标编译器来构建内核, 无论何时,只要你在开发机器上构建程序,并希望在内核的目标机器上运行, 你可以使用yourOsTargetArch-yourChosenFormat-yourOs编译器,它有一个/include和/lib目录,其中充满了你自己操作系统的本机系统包含和库。

稍后,当你确信可以从内核中开发和使用内核时,你将希望停止在单独的 “开发” 机器上构建所有程序。 更具体地说,在这一点上,你希望你的“构建”机器与测试机器相同,这样你就可以从操作系统内部构建在操作系统上运行的程序。 此时,是执行最后一步的时候了,在你单独的“开发”机器上,构建一个将在你的操作系统(host = yourOsTargetArch-yourChosenFormat-yourOs)上运行的编译器,并针对你的操作系统(target=yourOsTargetArch-yourChosenFormat-yourOs)。 这个交叉编译器,即GCC_Canadian_Cross,将允许你在自己的操作系统的用户空间上运行时本地编译其它程序。 它本质上是一个“发行版原生编译器”,就像发行版附带的编译器一样。 从这时起,就有了一个在你的操作系统上运行并面向你的操作系统的编译器,你基本上可以更自由地移植程序,并允许人们在运行你的操作系统时执行同样的操作(假设你将本地编译器与你的操作系统一起打包)。

我什么时候不需要交叉编译器?

如果你创建了一个真正的操作系统,并设法将gcc移植到它,那么gcc将生成与i686-myos-gcc完全相同的代码。 这意味着你不需要在你自己的操作系统上进行交叉编译,因为那里的GCC已经在做正确的事情了。 这就是linux内核使用Linux gcc而不是Linux交叉编译器构建的原因。

一个具体的例子

  • 我的Ubuntu机器上的gcc -v* 使用结果:
gravaera@gravaera-laptop:/void/cygwin/osdev/zbz$ gcc -v
Using built-in specs.
Target: i486-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.4.3-4ubuntu5' \
--with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ \
--prefix=/usr --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib \
--libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 \
--program-suffix=-4.4 --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-plugin --enable-objc-gc \
--enable-targets=all --disable-werror --with-arch-32=i486 --with-tune=generic --enable-checking=release \
--build=i486-linux-gnu --host=i486-linux-gnu --target=i486-linux-gnu
Thread model: posix
gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5)

有人构建了在我的机器上运行的编译器,他在和我一样的机器上构建了它,一台‘i486-linux-gnu’(构建机),并且打算让它在像他/我这样的机器上运行: 同样是i486-linux-gnu(主机),他的意思是这个编译器是他为我构建,发出针对我的机器的可执行文件,这样当我编译程序时,结果程序将能够在i486-linux-gnu主机上运行。 因此,他使编译器目标平台为i486-linux-gnu。

另见

文章