交叉编译和ABI简介


最近处理一个问题,需要在Ubuntu下使用GCC编译出多个平台版本做验证,发现对交叉编译这块有点模糊。导致工作效率略受影响,因此打算学习一下。

交叉编译

交叉编译器(Cross Compiler)就是一个可以编译在别的平台运行的程序的编译器。例如在Windows上编译安卓APK的编译器就是交叉编译器。
在交叉编译中,通常将编译可执行文件或者库文件的机器称之为构建平台,而将这些可执行文件或者库文件运行的平台称为宿主平台。
导致交叉编译器出现主要有一下几个原因:

  1. 某些设备资源有限无法运行编译器,例如8051单片机,显然不能指望它自己编译程序,它的资源有限到甚至系统都没有;
  2. 需要对一套代码编译出在不同平台运行的版本,比如你想让你的程序可以运行在Windows,Ubuntu,Mac等不同系统,又不想在每一种上配置一遍编译环境;
  3. 可以多台机器联合编译,提高效率;
  4. 为刚出现的机器编译它的系统和编译器等。

交叉编译器,并不是仅仅只有一个编译器,他还涉及链接器、调试器、标准库等等,这些统称为工具链。
在GNU中,对交叉编译器的命名有一个非强制的约定,交叉编译器的命名形式为:name-arch-[vendor]-os-[abi] (os = system / kernel-system),例如gcc-arm-linux-gnueabi就是编译的目标平台是基于AMR芯片的Linux上,由于这个名字只是个约定,因此并不是大家都遵守。可能顺序上有些不同,例如可能vendorh会在arch之前。但是一般情况都会把目标平台的架构、系统以及ABI等表示清楚。
因此交叉编译其实要做的就是找到对应的交叉编译器,最常用的就是确定目标平台的硬件和系统,如果是比较主流的硬件和软件产品例如树梅派,一般厂商都会有提供。特别是基于GCC的交叉的交叉编译器,例如上面提到的gcc-arm-linux-gnueabi,其实使用上和gcc没什么特别的,主要就是它们默认指向的头文件路径和生成的二进制文件不同而已。

此外,说到交叉编译,一个必不可少的知识就是ABI。

Application Binary Interface

ABI,全称(Application Binary Interface)一套说明程序如何在某个平台上运行的规则。平台的开发者定制这套规则,这个平台可以是硬件平面,也可以是操作系统层面。定制这个规则的主要目的让编译器了链接器知道如何去编译链接程序,让他们能够按照预期在平台上运行,也使得不同程序之间的交互成为可能。这里不同的程序可以是不同语言编写的程序编译成的二进制文件,也可以是同种语言编译成的库文件,还可以是同一个程序内部不同的函数调用。
ABI主要包含以下几个部分:

  1. 定义了函数调用标准,这是最核心的一套规则,它规定了如何将你调用一个函数这一行代码翻译成机器代码;
  2. 指导如何表示一个需要暴露的函数,也称之为“改名(name mingling)”;
  3. 定义可以数用那些数据类型,以及它们在内存中的布局。每种数据类型占用多少个字节,数据是大端格式还是小端,等等。这些都需要一个标准,不然不用说不同语言之间相互不能交互,就连同一种语言不同编译器编译出来的二进制都可能不同。
  4. 堆栈的结构和行为方式等多个方面,比如栈向上还是向下生长等等。

我们知道API是别的(当然也可以是编写者自己)程序员使用的。ABI的使用者主要就是编译器了,一般的程序员不可能也没必要参与到ABI的定制和直接使用ABI。因此一般人没必要了解ABI的细节。文末附有因特尔 Nios® II 处理器的ABI标准,感兴趣可以了解一下。这里只简单说一下ABI中的程序调用,对ABI有个具体的了解。

如果学过汇编,就很容易理解这个函数调用标准是什么,毕竟我们一般接触最底层的应该是汇编了,直接写机器指令的我相信有,但是我是没机会见过了。在汇编中,我们调用一个函数,主要做下面几个事情:

  1. 保存现场。计算单元所拥有的寄存器毕竟有限,所以我们要将目前正在执行的命令的地址、寄存器的数据保存起来,使得当从调用的函数返回后,CPU 还能正常工作;
  2. 设置调用函数所需要的参数;
  3. CPU 调转到函数所在地址继续执行;
  4. 程序结束后将需要返回给调用者的数据保存、清理;
  5. 恢复现场;
  6. CPU 继续执行。

我们用高级语言编写的程序,编译器最终会帮我们翻译成汇编代码,最终翻译成机器代码。

感觉大家基本都这么做的。如果所有人原本就这么做,那何必多此一举定一个标准?因为上面提到的这几不还很模糊,不具备可操作性。例如,保存现场,是保存在哪里?参数传递是通过特殊寄存器传递还是通过栈传递?返回值也类似?函数调用完成后谁负责做清理工作,调用者自己清理还是特定人员去清理?这里的每一条都要说的清清楚楚明明白白。然后编译器按照对应平台所设定的标准去生成正确的函数调用的汇编代码。
当然,上面值是简单说明了一个程序内部的函数调用的汇编代码生成,其实还有调用外部函数库的函数的详细步骤,也需要一一列举出来,编译器和链接器才能正确知道如何找到和调用它们。

除了ABI,还有一个EABI。其中的E 表示Embedded,是用于嵌入式系统的ABI,主要目的是提升系嵌入式系统的性能。EABI和ABI的主要区别是EABI去掉了用户代码和系统内核之间的抽象,可以让用户代码直接访问硬件,提高了性能。

公众号二维码

本文首发于个人微信公众号TensorBoy。微信扫描上方二维码或者微信搜索TensorBoy并关注,及时获取更多最新文章!

References

[1] https://en.wikipedia.org/wiki/Cross_Compile
[2] https://www.gnu.org/software/libtool/manual/html_node/Cross-compiling.html
[3] How to Survive Embedded Linux: How to Compile
[2] n2cpu_nii51016.pdf

发布了45 篇原创文章 · 获赞 4 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ZM_Yang/article/details/104676849