本文介绍了C6000最新的v7.2或者之后的编译器如何支持ELF(EABI)和COFF-ABI格式,首先由ARM引入嵌入式(Embedded)
EABI的介绍,之后比较了COFF-ABI和EABI的区别,如何用编译器选项(--ABI=EABI
--strip_coff_underscore)和预编译处理命令来实现从COFF格式到ELF格式的转换,主要是关注long数据类型位宽不一致以及汇编文件变量和函数定义的前置下划线的处理方式。最后是ELF格式引入的链接段区和COFF格式的不同。

EABI简介

ABI的定义:ABI代表应用程序二进制接口(Application Binary Interface).ABI是一套编译器遵循的规则,因此单独编译的对象模块和库可以链接成可执行文件。这些规则包括许多细节。例如:

  • 对象文件格式 (如EABI的ELF)
  • 如何传递给函数的参数
  • Char、 int、 long长整型等数据类型包含有多少位等。
  • C ++模板如何获取实例化
  • 以及其他

COFF ABI和EABI

C6000 编译器在版本7.2.x之前的版本支持COFF的ABI,从编译器版本v7.2.x 开始,C6000 编译器继续支持COFF,但也通--abi=eabi编译选项支持新的 EABI,除了mv6600外,其他默认的都是COFF ABI。而mv6600的默认为EABI格式。

EABI的定义

EABI代表Embedded ABI。安腾 64 (Itanium 64 IA64) 是英特尔 CPU 体系结构。虽然今天不广泛使用,但一个非正式小组的还是为该体系结构建立了一套c++ ABI。IA64
ABI成为为整个GPP 社区的C/C++编译器事实上的标准。许多 GPP工具集提供的现代编译器功能依赖此ABI。TI 编译器也引入IA64 ABI的ABI。ARM为ARM CPU 体系结构建立的名为AEABI。AEABI的目的是允许从不同的编译器供应商链接在一起并在ARM 系统上执行代码。TI ARM 编译器 v4.4.0 引入了对支持开关--abi=eabi。

ELV vs EABI实现

ELF是目标文件格式。EABI是一种ABI。ELF目标文件格式是EABI引入的最大的变化。

http://processors.wiki.ti.com/index.php/C6000_EABI:Introduction_to_EABI

C6000编译器支持ELF格式的EABI

C6000编译器支持ELF格式的EABI,包括如下特性

? 全局变量在main函数运行前初始化为0;

? 动态链接,很方便的在运行系统中加入算法;

? 原生的ROM:创建和链接ROM的代码;

? GPP样式的32-bit long:方便移植算法到通用处理器

? 更快的链接: 删除复制的debug信息

? 全局变量的初始化的压缩:节省用于初始化的表格;

C++的支持

? 有效的小类:加速C++的运行;

? 加快模板的实例和函数内联;

? 引入没有运行时额外开心的异常

? 更小的虚拟表节省空间

而COFF格式到EABI格式的转换也是非常容易的,在7.2之后版本的编译器通过编译选项就能完成。详细的可以参考

http://processors.wiki.ti.com/index.php/C6000_EABI_Migration.

C6000编译器EABI格式支持

C6000 EABI

7.2.X 引入了新的基于 ELF的ABI,C6000嵌入式应用程序二进制接口应用程序报告(SPRAB89) 中,可以找到C6000 EABI实现的详细信息。TMS320C6000 优化C编译器用户指南中可以找到的文档所述的功能(SPRU187,修订P或更高版本)和TMS320C6000 汇编语言工具用户指南(SPRU186,修订R或更高版本)。

最常见的用户EABI转换问题

大多数用户只需要执行几个到他们从 COFF 移到ELF的代码更改。用户很可能会遇到的最常见的问题是:

大小长已经改变从 40 位到 32 位,和原生类型__int40_t 现在支持为 40 位计算。

COFF 符号名称添加前导下划线,但EABI实现不用。汇编文件引用符号将需要特殊处理。

COFF 支持将会被淘汰吗?

ELF和EABI实现将最终完全取代COFF和COFF ABI;然而COFF将继续支持一段时间。COFFABI支持将慢慢地逐步取消。

  • 更多的编译器功能只有在EABI模式下支持,如动态链接就不在COFF格式中支持。
  • TI 新的CC6000系列处理器架构的默认ABI将为EABI实现模式,但可以通过--abi=coffabi选项生成 COFFABI。
  • 最终基于反馈,新编译器将完全停止支持COFFABI。此时将要求用户使用旧版本的编译器编译COFF ABI。

转换策略

首先考虑是否需要任何EABI实现功能。确认后再支持ELF和COFF格式。

COFF格式和ELF 格式库

强烈建议发布COFF和ELF版本的库。对可移植的C代码,支持COFF和ELF的工作量很小,更重要的是处理汇编代码,通常是使用条件编译重命名全局符号。

预定义的符号: __TI_EABI__

编译器和汇编程序预定义符号 __TI_EABI__以指示在编译源下EABI实现。使用--abi=EABI来指定该宏定义选项。

#if defined(__TI_EABI__)  
static char abi[] = "EABI";  
#else  
static char abi[] = "COFF ABI";  
#endif  
printf("ABI used: %s\n", abi); 

处理只有COFF格式的库

没有工具支持将COFF对象文件转换为ELF文件,因而强烈建议您发布COFF和ELF版本的库。

C和C++实现的更改

完全在C或C++编写的程序非常容易转换,只要满足可移植的条件:

  • 不依赖于C标准类型定义的准确大小,如int必须为4字节;
  • 不假设特定的位字段布局,大小端处理位域不同
  • 不假设特定枚举类型大小
  • 不使用intrinsic内部函数
  • 不使用 asm("") 语句

如果您的代码可以避免这些可移植假设,则可重用代码,下面介绍EABI实现和 COFF ABI不同的C和C++语言的功能。

long int 类型为 32 位

根据需要EABI标准,long int的整数类型是 32位宽,但在COFFABI模型中是40比特位宽。可以采用新类型__int40_t来表示40位精度。当包括 stdint.h,可以使用 int40_t 类型。

40 位intrinsic内部函数类型更改

C6000 EABI实现不支持 '长' 的本机 40 位整数类型,因为本机 40 位以下的内部函数将有新的原型,利用 COFF 和EABI实现下的全力支持,__int40_t 的类型:

__int40_t

_lsadd

(int,__int40_t)

__int40_t

_lssub

(int,__int40_t)

__int40_t

_labs

() __int40_t

__int40_t

_dtol

(double)

__int40_t

_ldotp2

(int,int)

int

_sat

() __int40_t

unsigned int

_lnorm

() __int40_t

double

_ltod

() __int40_t

位字段布局

位字段声明的类型是现在的容器类型。这意味着某些结构在 COFFABI和EABI实现将会有不同的布局。对于必须是COFFABI和EABI之间的可移植代码,不应使用位域。如果必须使用它们,位字段可能需要使用不同的有条件地编译代码声明。

C 和C++ 标准要求的位字段

位字段声明的类型是出现在源代码中的类型。为保存位字段的值,C 和C++ 标准允许执行分配任何可寻址的足够大存储单元,不需要与声明的类型相关。容器类型,通常称为可寻址的存储单元,是位字段如何包装并对齐的主要决定因素。

C89、C99和C++对类型声明有不同的要求:

  • C89 int,unsigned int,signed int
  • C99 int、unsigned int,signed int,_Bool 或者"一些其他实现定义类型"
  • C++的任何枚举的类型,包括 bool

在严格的C++,没有long long的类型,但是C99有它,因为C++编译器通常支持它作为扩展名。C99标准不需要的位字段支持long或long long已声明类型的实现,但因为C++允许它,而常见的C编译器并不能很好的支持它们。

TI 的编译器支持在C和C++使用任何整型类型作为声明的类型,但只针对EABI实现。ABI (coff),为位字段的声明类型 为int,unsigned int,signed int。

EABI位域布局方案

为EABI实现,声明的类型也用作容器类型。这有两个主要的后果:

  • 结构将会至少和声明的类型一样大
  • 如果当前容器中没有足够的未使用的空间,位字段将对齐到下一个容器。

如果 1比特位字段已声明类型int,EABI为位字段分配int容器。其他字段可以共享该容器,但要保证为每个字段存储在容器中完全相同的位字段的大小。

示例 1 (P 代表填充):

结构 S {int a:1 ;} ;

1111111111222222222233

01234567890123456789012345678901

aPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP (一个 32 位容器)

示例 2:

结构 S {int a:1 ; int b:1 ;} ;

1111111111222222222233

01234567890123456789012345678901

abPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP (一个 32 位容器

示例 3:

结构 S {char a:7 ; char b:2 ;} ;

111111 0123456789012345 aaaaaaaPbbPPPPPP (两个 8 位容器)

4 示例:

结构 S {char a:2 ; short b:15 ;} ;

1111111111222222222233

01234567890123456789012345678901

aaPPPPPPPPPPPPPPbbbbbbbbbbbbbbbP (一个 8 位容器、 一个 8 位垫和一个 16 位的集装箱)

IA64C++ABI规格 (可以参考位字段布局http://www.codesourcery.com/public/cxx-abi/abi.html).

COFF ABI布置方案

COFF ABI则使用不同的策略来处理位字段。它使用最小可能的空间,并将增长当前容器,如果不断增加,以允许分配的当前位置的位字段。

示例 1:

结构 S {int a: 1 ;} ;01234567 aPPPPPPP (一个 8 位容器)

示例 2:

结构 S {int a: 1 ; int b:1 ;} ;01234567 abPPPPPP (一个 8 位容器)

示例 3:

结构 S {char a: 7 ; char b:2 ;} ;111111 0123456789012345 aaaaaaabbPPPPPPP (一个 16 位容器)

4 示例:

结构 S {char a: 2 ; short b:15 ;} ;1111111111222222222233 01234567890123456789012345678901 aabbbbbbbbbbbbbbbPPPPPPPPPPPPPPP (一个 32 位容器)

更改代码的汇编 (C 和C++ABI变化)

C ABI是编译器对C代码程序的汇编表示。定义一个C可调用的函数或调用C函数的汇编代码必须符合ABI。本节介绍由 C和C++ 的功能在汇编代码中实现的方式更改而必须对汇编代码的修改。

COFF下划线名称重整

COFF-ABI使用下划线将汇编代码的名称空间和C代码命名空间分开。C 编译器将为每个外部可见的标识符加下划线,这样它将不会与汇编具有相同名称的对象发生冲突。我们把这称之为 COFF 下划线.

例如,此源的代码:

int x ;int func (int y) {}

在ABI COFF 变为:

.bss _x,4,4

_func:

请注意如何 C/C++的符号'x'已成为汇编的'_x'。

EABI实现不添加 COFF 下划线。 这是一个通用的 ELF 要求。用户负责确保不会冲突用户定义的名称。在EABI实现成为相同的源代码:

.bss x 4,4

func:

删除 COFF 下划线

COFF-ABI C和C++的符号,以与手动编码的汇编避免名称冲突添加前导下划线,但EABI实现不添加此下划线。因而需要修改COFF ABI 程序到EABI实现中的手动编码的汇编文件中使用的函数和变量。

条件重定义方法

将与 COFF 和 EABIs 兼容的首选的解决方案是使用.asg 汇编程序指令EABI实现C名称替换 COFF ABI 重整的名称。

.if __TI_EABI__

.asg red_fish, _red_fish

.endif

.global _red_fish

_red_fish:

<start of function>

双标签法

另一个简单的解决方法是提供两个标签,一个提供 COFF ABI名称,另一个提供EABI实现名称。

.global _red_fish、 red_fish

_red_fish:

red_fish:

< 函数的开始 〉

向后兼容性: --strip_coff_underscore

对于汇编代码不能轻易修改的项目,编译器提供了--strip_coff_underscore 选项指示把COFFABI外部符号名称翻译为EABI,即去除一个前导下划线。此选项仅对手代码的汇编文件和从线性汇编文件的编译器生成的汇编文件生效。此选项不会影响C和C++源代码,编译器生成的汇编文件,也不会采取影响链接器命令文件中。

例如,使用此源代码:

main.c:

int main () {red_fish() ;}

fish.asm:

.global _red_fish

_red_fish:...

对于 COFFABI在命令行上输入:

cl6x main.c fish.asm -z

为EABI实现在命令行上输入:

cl6x main.c fish.asm --ABI=EABI --strip_coff_underscore -z

链接器命令文件的更改

从COFF移植到EABI实现最有可能的步伐,用户将需要进行更改时链接器命令文件。链接器支持链接器命令文件预处理。请参阅 C6000 汇编语言工具用户指南。

EABI实现段

EABI实现重复利用 COFF ABI所使用的大多数编译器生成的段名称,还引入了新的段名称。每个段需要分配给相应的内存。请参阅 下面 生成工具套件的所有部分。

DP 相对数据节

EABI实现引入以下DP相对数据节:

.rodata

只读数据初始化

.neardata

近读写数据初始化

这些部分,则类似于.bss,只是它们初始化包含在对象文件中的数据。三个 DP 相对部分必须是连续的,这最容易通过使用链接器命令文件中的组:

GROUP (NEAR_DP_RELATIVE)

{

.neardata

.rodata

.bss

} > BMEM

编译器v7.2中的链接器将自动创建上述组并分配给 BMEM,如果链接器命令文件具有以下:

.bss > BMEM

只读段

EABI实现引入了以下的只读部分

  • .init_array 数据,用于注册C++ 全局变量的构造函数。
  • .c6xabi.exidx 的数据、 索引表用于C++ 异常处理
  • .c6xabi.extab 数据,存放C++ 异常手柄的说明

.init_array段和.pinit功能类似,但EABI实现不使用名称.pinit。

读写部分

EABI实现引入了读写以下各节:

  • .fardata 初始化far数据

V7.2 链接器将分配 ELF 节.fardata.far 段来实现EABI转换。

没有前置下划线

任何引用或链接器命令文件中的定义将需要更改。例如,要将符号设置为main函数。

ABI (COFF):

mainaddr = _main ;_symbol = 0x1234 ;

EABI实现:

mainaddr = main ;symbol = 0x1234 ;

C6x EABI实现段

图1. C6000目标文件EABI链接段区含义

Reference:

http://processors.wiki.ti.com/index.php/C6000_EABI_Migration

http://processors.wiki.ti.com/index.php/EABI_Support_in_C6000_Compiler

http://houh-1984.blog.163.com/

本文介绍了C6000最新的v7.2或者之后的编译器如何支持ELF(EABI)和COFF-ABI格式,首先由ARM引入嵌入式(Embedded)
EABI的介绍,之后比较了COFF-ABI和EABI的区别,如何用编译器选项(--ABI=EABI
--strip_coff_underscore)和预编译处理命令来实现从COFF格式到ELF格式的转换,主要是关注long数据类型位宽不一致以及汇编文件变量和函数定义的前置下划线的处理方式。最后是ELF格式引入的链接段区和COFF格式的不同。

04-15 03:07