开发PowerPC嵌入式应用二进制接口(EABI)应该遵循的原则

P.S:个人翻译,仅作参考,如有疏漏,欢迎各位友好交流并恳请各位提出指正。

PowerPC嵌入式处理器应用笔记

开发PowerPC嵌入式应用二进制接口(EABI)应该遵循的原则

摘要-这份应用笔记描述了PowerPC的嵌入式应用二进制接口(Embedded Application BinaryInterface , EABI),主要是提供给将汇编语言和高级语言结合起来变成的软件开发者以及软件开发工具供应商。EABI是一个集合,包含了各种嵌入式应用和开发工具需要遵循的一系列原则,目的是为了保证实现的一系列功能具有兼容性。他提供给软件开发者从各种各样的库以及各种可以的开发工具中进行选择的机会。只要他们兼容EABI标准,他们就能保证互相兼容,正常工作。

1.概要

一个应用二进制接口(Application Binary Interface , ABI)指定了一个为程序和系统软件准备的接口。嵌入式应用二进制接口(Embedded Application BinaryInterface ,EABI)起源于PowerPCABI Supplement和UNIX的系统VABI标准。PowerPCABI Supplement是为了服务器操作系统(例如IBM的AIX和苹果的MacOS)而设计的。EABI和PowerPC的ABISupplement 不同,他的目的主要是为了减少内存使用量,并且优化执行速度,因为这些都是嵌入式软件的主要需求。EABI描述了使用寄存器、传递参数、栈的组织、小数据区、目标文件还有执行文件格式的约定。这篇笔记主要涵盖了以下的EABI话题:

-         开发工具文件格式

-         数据类型和对齐方式

-         寄存器的使用约定

-         栈帧的创建和组织

-         函数参数的传递

-         小数据区域的使用和组织

2.文件格式

遵从EABI标准的目标和可执行文件是以ELF(Extended Linking Format)文件存在的,并且调试信息是以DWRF(Arbitrary Record Format)格式保存在Debug目录下。现在的DWARF标准是1.1.0。这里有另外一个DWARF 2.0.0的版本推荐,因为他加入了对C++代码调试的支持特性,虽然现在还不是一个官方标准,但是许多工具的开发者已经使用或者说使用接近DWRF2.0.0标准了。

3.数据类型和对齐

PowerPC 的体系结构定义的标量(整数)数据类型大小如下表表1所示。


所有的数据类型在内存和栈帧里都是对齐的,都在他们大小的整数倍的地址上。距离来说,一个字,因为他的大小是4B,在地址上对齐的话就是被4整除的。板子的话最终是能被2整除,一个例外情况是四字,当他们不在一个联合或者结构体内时,他们只需要按照8B对齐就可以。数组的对齐就按照数据元素的类型来进行对齐即可。

一个结构体(或者联合)是根据结构体中的对齐所占空间最大的成员来决定自身的对齐的。比如说,这个结构体包含一个双字,这个双字成员必须从能被8整除的地址起始,那么可能就要在他之前和之后的成员过后进行字节填充(以使结构体满足8整除)。这个结构体的大小通常是这个结构体对齐的数倍。遵从EABI标准的编译器和汇编器会自动为数据分配创建正确的对齐空间,但是在一些应用中需要填充的部分可能会导致一些问题。举一个网络协议的数据包的例子,他就有着比较特殊对齐要求。对于一些编译器来讲,他们支持关闭对齐特性或者用不同的边界值进行覆盖,比如IBM的高级C/C++编译器有#packpragma来达到这些目的。因为不对齐的数据访问需要更多的总线周期用来读写,甚至可能会导致进入软件导致的中断,性能会大大下降,所以应该尽可能的避免不对齐的数据访问。

下表显示了ANSI C语言的数据类型和他们的大小。对于所有的类型,NULL被定义为值为0.有符号和无符号的整形类型有着同样的大小。


4.寄存器使用约定

PowerPC的体系架构有32个通用寄存器(generalpurpose registers, GPRs)和32个浮点寄存器(floating-point registers, FPUs)。EABI把寄存器分为易失的,非易失的以及专用的三类。非易失性寄存器必须保存他们的原始值,因此修改了非易失性寄存器的函数必须在返回调用函数前恢复他们的原始值。易失性寄存器不需要在函数过程中进行保存。有3个非易失性的GPRs寄存器有其他专用,他们是R1,R2,R13。R1是用来当栈帧指针的(相当于SP),R2是用来当只读的小数据区的基指针的(anchor),R13是用来当可读可写的小数据区的基指针的。专用寄存器不应该被用做其他用途,即使是临时性的也不行,因为他们随时可能会在中断处理器中被用到。所有的PowerPC处理器和他们的使用方法,在下表。


5.栈帧约定

PowerPC的体系结构没有PUSH/POP指令来实现一个栈。EABI的栈帧创建和使用约定是用来支持参数传递,非易失性寄存器保存,局部变量(local variables),和代码调试的。他们的功能是通过把各种各样的数据用同样的方式放到栈帧里的方式实现的。每个函数不管是调用另外一个函数还是修改了一个非易失寄存器都必须从内存中创建一个栈帧预留出来作为运行栈使用。如果一个函数是叶函数(意思是他不会调用其他函数),并且没有修改任何非易失寄存器,那么他就没有必要去创建栈帧。

SP总是指向现在正在执行的函数的栈帧的最低地址。每个新的帧在建立的时候都是与最近分配的帧相毗邻的,并且是向下生长的。栈帧创建是通过在函数开头时分配函数所需要的所有的空间并且把SP一次性减掉完成的。为了保证SP的更新是不会被中断的原子操作,需要使用stwu指令(a store-with-update)。在(一个函数的)开头也将保存任何在函数中会使用的非易失寄存器进栈帧。下面的是一个函数开头的例子:

FuncX: mflr %r0 ; Get Link register

stwu %r1,-88(%r1) ; Save Back chain andmove SP

stw %r0,+92(%r1) ; Save Link register
stmw %r28,+72(%r1) ; Save 4 non-volatiles r28-r31

P.S 这里是译者自己备注,后面的图表也可以看出来,每个函数的栈帧里面保存的LR是调用函数时的LR(而不是自己的返回地址),但是这个保存的动作是由他调用的函数在函数开头完成的,这也是为什么上面的LR +92会比-88大4。

                   在这个函数返回他的调用函数之前,栈帧会在函数的结尾处,通过将SP加上现在的栈的大小从而被清除。这个结尾部分的代码恢复所有在开头保存的寄存器,通过增加SP的方式释放现在的栈帧,然后回到调用函数。下面的是一个和上面代码相呼应的函数结尾部分:

lwz %r0,+92(%r1); Get saved Link register

mtlr %r0 ;Restore Link register

lmw%r28,+72(%r1) ; Restore non-volatiles

addi %r1,%r1,88; Remove frame from stack

blr ; Return tocalling function

表1展示了栈帧规则,用的是一个2层级的函数调用距离。在第一时间,函数A退出并且调用了函数2,在第二时间,B的开头代码执行并且创建了B的帧,在第三时间B调用了C并且C的开头代码执行,在第四时间,函数C终结,并且C的结束代码通过加SP的值得方式摧毁自身的帧。

         

栈帧总是按照双字(8B)对齐的,有必要的时候会使用填充字节的方式对齐。

表2表明了帧的组织结构,包含了所有的可选区域。


所有的栈帧都有一个头部,包含了2个区域:回溯字和LR保存字(theBack Chain Word and the Link Register (LR) Save Word)。回溯字包含了前一个栈帧的回溯字的地址从而组成了一个栈帧的链表。回溯字总是在栈帧的最低地址。LR保存字是用来在修改LR之前就保存现在的LR的值,在进入一个子程序时的LR,代表了返回调用函数时的返回地址。他的位置紧接着回溯字区的上方。

   函数参数区(Function Parameter Area)是一个可选区域并且大小不定。他包含了当R3~R10作为参数寄存器都全使用了之后还有的额外的函数参数。他位于LR保存字的上方。局部变量区(Local Variables Area)是用来当易失性寄存器(这些寄存器可以用来保存本地变量)都用完了的情况下保存额外的本地变量。如果一个函数修改了任何非易失条件寄存器(Condition Register,CR),就必须把完整的CR保存在CR 保留区域(CR Save Area)。

   通用寄存器保留区(The General Purpose Register(GPR) Save Area),是可选的并且大小可变。当保存任何通用寄存器(GPR),所有的GPRs从(此寄存器开始)最小的到R31,都要全部被保存。距离来说,如果一个函数修改R17,他必须创建一个栈帧大到他的GPR保留区可以保存R17~R31。同样的规则也适用于保存浮点FPR的非易失性寄存器的浮点保留区(Floating point Register Save Area)。在没有浮点硬件的PowerPC上实现代码时,不需要创建FP保留区,因为没FPRs用来保存。

6.参数传递

对于PowerPC处理器来讲,一个更有效率的传参方式是通过寄存器而不是用内存。R3-R10都是可以用来传递标量参数的,返回值则可以保存在R3和R4里面。同样的F1-F8是用来传递浮点类型的参数,而浮点型返回值放在F1里面。如果这里有超过8个参数需要传递,那么额外的参数所需要的空间将会在函数的栈帧的Function Parameters Areas中分配出来。同样的,额外的返回值也会存在这片区域。下面的C预言片段举例展示了这种规则:

#include"stdio.h"
void func1(int);
int var1;
main(){
var1 = 4;
func1(var1);
}
void func1(int arg1){
printf("func1 - arg1 value: %d\n",arg1);
}

为了实现C语言声明,下面的汇编指令阐明了加载和传递func1中的var1的值。在var1被设置为4之后,var1的值被加载到R3l来把他作为一个参数传递。LWZ指令是用来加载R3的。注意,在设置var1=4的指令过后,R12包含了var1的高16位地址,因此被用LWZ用到。R3被用到因为他是第一个可以被用来做整型参数传递的寄存器。

var1 =4;

li%r11,4

addis%r12,%r0,var1@ha

stw%r11,var1@l(%r12)

func1(var1);

lwz%r3,var1@l(%r12)

blfunc1

7.小数据区(SDAs)

EABI标准有一个被称为Small Data Area(SDA)的结构,这是用来利用PowerPC的基址加移位寻址模式的。移位是一个有符号的16位的值,因此即使在不改变基址寄存器的情况下,也可以寻址64kB(位移是+32K或者-32K)大小的地址空间。16位的位移,和指令的操作码一样,也满足一个指令字。这表明,当我们在存取一个变量时,这是一种比用32位地址来引用更加有效的方式。这是因为32位地址需要两个指令字而这种方式只需要1个指令字。SDAs对于全局变量和静态变量以及常量都非常有用。

  这儿有两块SDAs,一个是用于读-写变量,第二个是用于只读变量。当C的运行环境被初始化时,小数据区被一个一次性加载的基址寄存器所引用。R2是用做只读(常量类型)小数据区的基址寄存器,而R13是用做读写(非常量类型)的小数据基址。

         在以R13为基址的小数据区的变量,他们被包含在两个ELF的段里面,要么是.sdata段,要么是.sbss段。如果是初始化的读-写变量,使用.sdata段,如果是没有初始的读写变量,那么使用.sbss段。通常来讲,.sbss段的变量在运行时会被赋默认值为0。因为这块SDA是可读写的,因此他必须存在于内存中。

         下面是一个从小数据区取出变量的指令的例子,他位于基址的正偏移32字节的位置。

lwz r29,32(r13)

在以R2作为基址的小数据区的只读变量,被包含下下面两个段里面:.sdata2和.sbss2。对于初始化了的只读变量,.sdata2被使用;对于没有初始化的变量,.sbss2被使用。通常,没有初始化的变量在运行时被默认赋值为0。因为这块SDA是只读的,所以他可能存在于ROM中只要.sbss2中包含的变量不被使用。

PowerPC体系结构里,当一些指令使用基址+位移的寻址模式时,把R0当做0。这些指令包括load,store以及各种缓存管理指令。因此,R0隐式的被看成第三类包含最低和最高的32KB的处理器内存地址空间的小数据区的基址。

在IBM的HighC/C++编译器中,可以用编译指示Push_small_data把变量布置到SDAs中。默认的话,全局变量不会被放到SDA里。这个编译指示可以用一条编译选项激活。选项“-Hpragma=Push_small_data(4;0)”指明,4B或者以下大小的读写变量会被存储在读写SDA中。他也表明,没有只读变量会被放到只读SDA中,因为第二个参数把(只读变量)大小指定为0。下面的C语言片段会帮助你阐述机器指令是如何存取一个全局变量的。

int var1;

main()

{

var1 = 4;

func1(var1);

}

下面的是编译器产生的汇编语言,为了把全局变量var1赋值为4,用了3条指令来存值到var1里。

li %r11,4

addis%r12,%r0,var1@ha

stw%r11,var1@l(%r12)

1.li 得到值并且放到R11里。

2.addis 被用来把var1的高地址半字存到R12里。

3.stw用了基址+位移的寻址方式。这个位置从R12的内容加上var1的低地址半字(得到的就是var1的地址值)。

         当在读写SDA区时,赋值var1需要的两条指令如下:

         li %r12,4
stw %r12,var1@sdaxr(%r13)

         注意,为了使用任意的SDAs,你需要C运行环境创建代码来初始化小数据区的基址寄存器。对于IBM的评估套件使用者来讲,你可以通过添加下面的代码到./samples/bootllib.s中完成,这一步在跳到_kernel_entry程序之前。宏命令_SDA_BASE_ 和 _SDA2_BASE会被链接器自动定义,如果相关联的SDAs被用到的话。对于_SDA_BASE_,这个值是一个基地址,这个基地址使所有的在.sdata和.sbss段中的数据都能被16位的有符号偏移数编址(For_SDA_BASE_, the value is the address to which all data in the .sdata and.sbss sections can be addressed using a 16-bit signed offset.)。如果一个SDA没有被用到,相关联的宏定义的值会被赋值为0。

!****************************************************************************

! INITIALIZATION OF BASE REGISTERS FOR SMALL DATA AREAS:

!****************************************************************************

lis%r2,_SDA2_BASE_@ha ! r2 is the read-only SDA anchor

addi%r2,%r2,_SDA2_BASE_@l

lis%r13,_SDA_BASE_@ha ! r13 is the read-write SDA anchor

addi%r13,%r13,_SDA_BASE_@l

         为了对比,表4列举了在Dhrystone测试中的代码大小和执行速度的结果变化。编译器使用的是IBM HighC/C++ V3.61,并且执行在IBMPowerPC 401GF开发板上,第一行表明的结果是使用的纯速度选项(-O6),并且允许内联函数调用最多150次(-Hic=150)而小于150个节点(-
Hit=150),第二行加入了对于小于等于8B大小的读写变量以及小于等于4B的只读变量的SDAs的使用。

        

8.总结

EABI通过ELF/DWARF文件格式标准让给供应商各自开发的工具具有兼容性。这个标准让开发者可以混合使用各种EABI兼容标准的部件来创建一个他们需要的软件开发环境工具链。另外,EABI标准对于寄存器使用和参数传递也允许独立开发的代码不需修改也具有可重用性。

9.参考文献

Thefollowing references are presented in a suggested reading order progressingfrom a comprehensive
overview through the standards documents from most PowerPC specific to mostgeneral.
· Programming PowerPC Embedded Applications, Embedded SystemsProgramming magazine,
December 1995. Back issue information available from embedded@halldata.com
· PowerPCEmbedded Application Binary Interface, Version 1.0, IBM and Motorola,January 10, 1995.
Available from ESOFTA at http://www.esofta.com/pdfs/ppceabi.pdf
· System VApplication Binary Interface, PowerPC Processor Supplement, SunMicrosystems and
IBM, September 1995. Available from ESOFTA at http://www.esofta.com/pdfs/SVR4abippc.pdf
· System VApplication Binary Interface, Third Edition, UNIX Systems Laboratories,1994
· DWARF Debugging Information Format, Revision 1.1.0, UNIX InternationalOctober 6, 1992.
Available from ESOFTA at http://www.esofta.com/pdfs/dwarf.v1.1.0.pdf


  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值