TMS320C54x系列DSP指令和编程指南——第2章 通目标文件格式介绍

时间:2021-06-13 15:48:31

第2章 通用目标文件格式介绍

汇编器和连接器可以产生在TMS320C54x器件上执行的目标文件,这些目标文件的格式称为通用目标文件格式(COFF)。采用COFF格式有利于程序的模式化编程,因为它支持用户在编写汇编语言程序时使用代码块和数据块,这些块称为段(section)。汇编器和连接器都提供了多个伪指令用来创建和管理段。本章对COFF段进行简要介绍。

2.1 COFF文件类型

COFF文件包括三种类型,分别是COFF0、COFF1和COFF2。每种类型具有不同形式的文件头,但是数据块的格式相同。

C54x的汇编器和C语言编译器产生COFF2格式文件,连接器可以读写所有类型的COFF文件,默认情况下连接器输出COFF2格式文件。用户可以使用连接器选项-v改变输出文件格式。连接器只能支持由早期版本的汇编和C编译器产生的COFF0和COFF1格式。

2.2 段

目标文件的最小单元称为段(section)。段就是一组代码或数据,最终将占据一段连续的存储器空间。目标文件中各个段是相互独立的。COFF目标文件必须包含以下三个默认段,它们是:

□    .text段    通常包含可执行代码。

□   .data段   通常包含已初始化的数据。

□   .bss段    通常用来给未初始化的变量保留存储空间。

此外,汇编器和连接器允许用户创建、命名和连接已命名的段,这些段可以同.text、.data和.bss段一样被使用。

段可分为两种基本类型:

□   已初始化段    包含数据和代码。.text段和.data段是已初始化段,用户使用.sect汇编伪指令命名所创建的已命名段也是已初始化段。

□   未初始化段    用来给未初始化数据保留空间。.bss是未初始化段,用户使用.usect 汇编伪指令所创建的段也是未初始化段。

用户可以使用汇编器伪指令将代码和数据适当地划分为多个不同的段。汇编器在汇编阶段生成这些段,创建如图2.1所示结构的目标文件。

连接器的功能之一是把段重新定位到目标存储器映像中,这种功能叫做重定位。因为大多数系统中都包含有多种类型的存储器,段可以帮助用户更有效地使用目标存储器。所有段都可以独立地进行重定位,用户可以将任何段定位到目标存储器的任意区域。例如,用户可以定义一个包含初始化程序的段,然后把这个程序分配到含有ROM的存储映像中。

图2.1所示为目标文件的段与目标存储器的映射关系。

TMS320C54x系列DSP指令和编程指南——第2章 通目标文件格式介绍

2.3 汇编器对段的处理

汇编器通过以下5个伪指令来标识属于某个段的汇编语言程序:

□   .bss
      □   .usect
      □   .text
      □   .data
      □   .sect

其中.bss和.usect伪指令创建未初始化段,其它伪指令创建已初始化段。用户可以给每个段创建子段,以便更细致地控制存储的分配。子段用.sect和.usect伪指令定义,基段段名和子段名之间加一个冒号来标识。

注意:默认段伪指令      如果用户没有使用任何段伪指令,则汇编器将所有内容都汇编到.text段。

2.3.1 未初始化段

未初始化段在目标文件中没有实际内容,仅仅是在存储器中(通常在RAM中)保留存储空间而已。程序在运行过程中可以使用这些空间创建和存储变量。

未初始化段由.bss和.usect汇编伪指令产生。

□    .bss伪指令给.bss段保留存储空间。
      □    .usect伪指令为一个指定的未初始化已命名段保留存储空间。

每次调用.bss伪指令,汇编器在相应的段中保留专门的空间。每次调用.usect伪指令,汇编器在指定的已命名段保留专门的空间。

这些伪指令的语法结构为:

.bss symbol, size in words [, [blocking flag] [, alignment flag]]
      symbol .usect ”section name ”, size in words [, [blocking flag] [, alignment flag]]

其中各个字段的含义如下:

□   symbol    指向由.bss或.usect伪指令所保留的第一个字。symbol对应于所保留空间的变量名。它可以被其它任何段引用,并且也能用汇编器伪指令.global定义成全局符号。
      □    size in words    是一个绝对表达式。如果是.bss伪指令,在.bss段中保留size个字;如果是.usect伪指令,则在已命名段section name中保留size个字。
      □    blocking flag    是一个可选的参数。如果该参数是一个非0值,汇编器将连续地分配size个字。分配的空间不跨越页边界,除非size大于一页,这种情况下该目标代码将从一个页边界开始。
      □    alignment flag    是一个可选的参数。
      □    section name    告诉汇编器为哪个已命名段保留空间。

.text、.data和.sect伪指令告诉汇编器停止当前段的汇编,并开始对指定的段进行汇编。.bss和.usect伪指令并不结束当前段也不开始汇编一个新的段,而是从当前段临时退出。.bss和.usect伪指令可以放在初始化段中任何位置,不影响其内容。

用.usect伪指令能够创建未初始化子段,汇编器将对未初始化子段和未初始化段以相同方式处理。

2.3.2 已初始化段

已初始化段包含可执行代码或已初始化的数据。这些段的内容存储在目标文件中,程序加载后则存放在存储器中。每个已初始化段可以独立地重新定位,并且能够引用在其它段中定义的符号。连接器自动解决这些与段相关的引用。

共有3个伪指令用来告诉汇编器怎样把代码或数据放置到某一个段内。这些伪指令的语法结构为:

.text [value]
      .data [value]
      .sect “section name” [, value]

当汇编器遇到上述伪指令之一时,就停止汇编当前段(相当于隐含一条终止当前段的命令),将后面的代码汇编到指定的段,直到遇到另一个.text、.data或.sect伪指令为止。如果给出了value值,就确定了段程序计数器(SPC)的起始值。SPC的起始值只能指定一次,而且必须在汇编器第一次遇到关于这个段的伪指令时指定。默认情况下,SPC从0开始。

段可以通过多次重复的过程建立起来。例如,当汇编器首先遇到一条.data伪指令时,.data段为空。第一条.data伪指令之后的源程序语句被汇编到.data段,直到汇编器遇到一条.text或.sect伪指令为止。如果汇编器再次遇到.data伪指令,将把跟随在该伪指令之后的源语句添加到已存在的.data段的语句后面。最后只产生单个.data段,它可以被分配到连续的存储空间。

已初始化子段能够用.sect伪指令产生,汇编器对已初始化子段和已初始化段以相同方式处理。

2.3.3 已命名段

已命名段是用户自己创建的段,使用起来与.text、.data和.bss等默认段一样,但是它们会被独立汇编。例如,重复使用.text伪指令在目标文件中产生单个.text段。连接之后,.text段在存储器中被当作一个单元分配空间。假设用户不想用.text给某部分可以执行代码(例如初始化程序)分配空间,可以将这部分代码汇编到一个已命名段,与.text段分开汇编,独立分配空间。用户也可以在.data段之外汇编已初始化数据段,也可以在.bss段之外给未初始化变量保留空间。下列伪指令用于产生已命名段:

□    .usect 伪指令产生类似于.bss段的已命名段,这些段为变量在RAM中保留空间。
      □    .sect伪指令产生的段类似于默认的.text段和.data段,可以包含代码和数据。.sect伪指令产生可重新定位的已命名段。

这些伪指令的语法结构如下:

symbol .usect ”section name”, size in words [, [blocking flag] [, alignment]]
      .sect “section name”

section name参数是段的名字,用户最多可以产生32767个已命名段,每个段名最多可以由200个字符组成。对于.sect和.usect伪指令,段名可以附带一个子段名。

每次使用一个新的段名调用这些伪指令时,就会产生一个新的段;如果调用这些伪指令时使用已经存在的段名,汇编器将代码或数据(或保留空间)汇编到以此名字命名的段中。不同的伪指令不可以调用相同名称的段。也就是说,不能用.usect伪指令创建一个段之后,又在.sect伪指令中定义该段。

2.3.4 子段

子段是段中的小段。与段一样,子段能够被连接器处理,使用户能够更加紧凑地分配存储器。子段也使用.sect或.usect伪指令产生,语法结构如下:

section name:subsection name

子段的表示方法是基段段名加一个冒号,再加子段名。子段可以独立分配空间,或者与其它属于相同基段的子段一起分配空间。例如,要想在.text段内创建一个名为_func的子段,就输入:

.sect ".text:_func"

用户可以单独分配空间给_func,也可以与.text段中的其它部分一起分配空间。用户可以创建两种类型的子段,一种是用.sect伪指令创建的已初始化子段,另一种是用.usect伪指令创建的未初始化子段。子段的存储空间分配方法与段相同。

2.3.5 段程序计数器

汇编器为每个段保留一个专用的程序计数器,称做段程序计数器(SPC)。SPC代表代码段或数据段中的当前位置。初始化时,汇编器将每个段的SPC设置为0,随着汇编器向段内填充代码或数据,就会使相应的SPC值增加。如果从其它段再次回到该段进行汇编,则汇编器复现SPC的先前值,并继续增加该值。

汇编器假定每个段都开始于0地址,由连接器最终确定每个段在存储器空间的实际地址。

2.3.6 使用段伪指令的实例

例2-1演示了使用段伪指令来交替地汇编不同的段,并逐步建立COFF段的过程。用户可以用段伪指令开始汇编某个段,或者继续汇编到一个已经包含代码的段。对于后一种情况,汇编器简单地新的代码添加到段内已经存在的代码之后。

例2-1是一个列表文件的格式,显示了汇编期间SPC被修改的过程。列表文件的每一行有以下4个字段。

□    区域1 包含源代码行计数器。
      □    区域2 包含段程序计数器。
      □    区域3 包含目标代码。
      □    区域4 包含原始的程序语句。

TMS320C54x系列DSP指令和编程指南——第2章 通目标文件格式介绍

例2-1中的文件创建了以下5个段,如图2.2所示。

□    .text    包含10个字的目标代码。
      □    .data    包含7个字的已初始化数据。
      □    vectors    由.sect伪指令创建的已命名段,包含两个字的已初始化数据段。
      □    .bss    在存储器中保留10个字。
      □    newvars    由.usect伪指令创建的已命名段,在存储器中保留8个字。

图2.2中第一列是源程序行号,第二列是汇编到这些段内的目标代码。

TMS320C54x系列DSP指令和编程指南——第2章 通目标文件格式介绍

2.4 连接器如何处理段

连接器有两个与段相关的功能:第一个功能是把通用COFF目标文件的段作为输入段,将输入段组合起来(当连接多于一个文件时),在可执行COFF输出模块中创建输出段;第二个功能是为输出段选择存储器地址。以下两个连接器伪指令支持上述功能。

□    MEMORY    该伪指令用来定义目标系统的存储器映像。用户可以为存储器的各个部分命名,并规定其起始地址和长度。
      □    SECTIONS   该伪指令告诉连接器将输入段组成输出段,以及将这些输出段放到存储器的何处。

子段使用户能够对段进行更为精确的处理,用户可以用连接器的SECTIONS伪指令指定子段。如果用户没有专门对子段做说明,那么连接器就将该子段与其它具有相同基段名的段组合在一起。

连接器伪指令并非必须使用,如果不用,连接器就采用默认的存储空间分配算法处理段;如果用户使用连接器伪指令,就必须在连接器命令文件中使用。有关连接器命令文件和连接器伪指令的详细说明参见第6章的连接器描述。

2.4.1 默认的存储器分配

图2.3所示为两个文件的连接过程。

TMS320C54x系列DSP指令和编程指南——第2章 通目标文件格式介绍

图2.3中,已汇编的一个目标文件file1.obj和file2.obj中各自包含了.text、.data和.bss 3个默认段,此外,每个文件中还各包含了一个已命名段。可执行的目标模块显示了段的组合方法。连接器将两个目标文件的.text段合并为一个.text段,同样,分别将.data和.bss段合并,再将两个文件中的已命名段放在最后。存储器映像说明了连接器在存储器中存放各个段的算法。默认情况下,连接器从080h地址处开始分配存储器空间。

2.4.2 将段放在存储器映像中

图2.3举例说明了连接器组合段的默认方式。有时用户可能不想使用默认方式,例如不希望所有的.text段合并成一个.text段,或者希望把一个已命名段放在通常存放.data段的地方,这种情况下用户可以自己分配存储器。应用系统的存储常常包含着多种类型(RAM、ROM和EPROM等)的、大小不等的存储器,用户可以按照自己的意愿把某个段放在某种特定类的存储器中。

2.5 重新定位

汇编器总是假设各个段从0地址开始,所有可重新定位的符号(标号)都是以段内0地址为参考。实际上不可能所有段都从0地址开始,连接器以下列方式对每个段进行重新定位:

□    将各个段分配到存储器空间,使每个段从适当的地址开始。
      □    根据新的段起始地址来调整符号值。
      □    根据调整后的符号值来修改对已重新定位符号的引用。

连接器使用重新定位入口来调整对符号值的引用。每当可重新定位的符号被引用时,汇编器产生一个重新定位入口。版本号被重新定位后,连接器使用这些入口来修正对符号的引用。

例2-2包含了一个产生重新定位入口的C54x代码模块。

TMS320C54x系列DSP指令和编程指南——第2章 通目标文件格式介绍

TMS320C54x系列DSP指令和编程指南——第2章 通目标文件格式介绍

例2-2中的符号X、Y和Z都是可重新定位的。Y定义在.text段中,而X和Z则在另一个段中定义。代码被汇编时,X和Z的址为0(汇编器指定所有未定义的外部符号值都为0),而Y值为6(相对于.text段中的0地址)。汇编器为X、Y和Z产生重新定位入口,对X和Z的引用是外部调用(在列表中用“!”字符表示),对Y的引用是一个内部定义的可重新定位符号(在列表中用“'”字符表示)。

代码经过连接之后,假设X重新定位到7100h,并假设.text段重新定位后从7200h开始,则Y重新定位后的值是7206h。连接器使用两个重新定位入口来修正两个对目标文件的引用:

TMS320C54x系列DSP指令和编程指南——第2章 通目标文件格式介绍

COFF目标文件中的每个段都有一个重新定位入口表。对于段内每一个可重新定位的引用,重新定位入口表都有一个相应的重新定位入口。连接器通常在使用完重新定位入口之后就将它删除,以防止输出文件被再次连接或加载时被再次重新定位。不包含重新定位入口的文件是一个绝对地址文件,它的所有地址都是绝对地址。如果希望连接器保留重新定位入口,可以在调用连接器的时候使用-r选项。

2.6 运行时重新定位

有时用户可能希望将代码加载到某个存储区域,却在另一个区域运行。例如,在以ROM为外部基本存储器的系统中,代码被加载到ROM中。然而用户可能对代码性能具有严格的要求,需要程序在RAM中更快速地运行。连接器提供了简单的方法来处理这种情况。通过SECTIONS伪指令,用户可以让连接器对一个段进行两次定位:一次指定加载地址,一次指定它的运行地址。对于加载地址,使用load关键字指定,对于运行地址使用run关键字指定。

加载地址决定了加载器把段的原始数据放在什么位置。任何对于段的引用(如段内标号等的引用)都要以运行地址为参考。系统运行时,必须先将代码从加载地址到运行地址中。由于运行地址是用户指定的,所以复制不会自动执行。

如果用户为某个段的加载和运行仅指定了一次存储器分配,则该程序段在同一地址加载和运行。如果用户给一个段分别指定了加载地址和运行地址,则该段实际占用了大小相同的两个地址空间。

未初始化段(例如.bss段)不会加载数据。因此,对它而言,只有运行地址才有意义。连接器只给未初始化段分配一次地址。如果用户既指定了加载地址又指定了运行地址,连接器将忽略加载地址,并发出警告信息。

2.7 加载一个程序

连接器的输入文件(目标文件)和输出文件(可执行文件)具有相同的COFF格式。但是可执行目标文件中各个段已经被组合,并重新定位到目标存储器,可以被直接加载到目标系统中。根据运行环境的不同,有不同的加载方法,下面是两种最常用的加载方法。

□    TMS320C54x调试工具,包括软件仿真器、XDS51x硬件仿真器、软件开发系统以及内置的加载器等。每种工具都有LOAD命令来调用加载器。加载器读取可执行文件,并将程序复制到目标存储器中。
      □    用户可以使用汇编语言工具包中的十六进制转换工具hex500将可执行的目标文件转换成EPROM编程器可识别的几种目标文件中的一种,然后用EPROM编程器将转换后的程序烧写入EPROM中。

2.8 COFF文件中的符号

COFF文件中有一个符号列表,保存着文件中的符号信息。连接器在重新定位时使用该符号表,调试工具也可以使用该符号表进行符号调试。

2.8.1 外部符号

外部符号是指在某个程序模块中定义,在另一个程序模块中引用的符号。用户可以通过.def、.ref和.global伪指令来定义外部符号。

□    .def    在当前程序模块中定义,在其它程序模块中被使用。
      □    .ref    在其它程序模块中定义,在当前程序模块中被使用。
      □    .global    可以是上述两种情况之一。

下面是一个实例:

TMS320C54x系列DSP指令和编程指南——第2章 通目标文件格式介绍

该例程中第三行“.def  x”表示定义了一个外部符号 x,其它程序模块可以引用它。第四行“.ref  y ”表示程序引用符号 y,但是y是由另一个程序模块定义的。汇编器把x和y都放在目标文件的符号表中。当该文件与其它目标文件一起连接时,对x的入口定义使其它文件能够引用未定义的x。y的入口使得连接器在其它文件的符号表中查找对y的定义。

连接器必须使符号的引用与相应的定义相匹配。如果连接器找不到所引用符号的定义,将报错。这类错误将中止可执行目标文件生成。

2.8.2 符号表

当汇编器遇到一个外部符号的定义或引用语句时,将在符号表中创建一个入口。汇编还创建每个段起始位置的特殊符号。连接器使用这些符号为段内定义的其它符号定位。

汇编器通常不为上述符号以外的任何符号创建符号表入口,因为连接器不用它们。例如,通常标号不包含在符号表中,除非该标号被声明为.global。为了便于符号调试,在符号表中为每个符号建立相应的入口有时是很有用的。要实现这一功能,需要调用汇编器时使用-s选项。