企业简介

作为中国自动化领域的权威旗舰网络媒体,控制网创立于1999年7月,是中国举行的第十四届IFAC (International Federation of Automatic Control)大会的中国官方组织机构的唯一指定网站。控制网是中国自动化学会专家咨询工作 委员会(ECC)的秘书处常设之地。是北京自控在线文化传播有限公司开设的网站。

  • 公司类型:其他

联系方式
  • 控制网
  • 地址:北京市海淀区上地十街辉煌国际2号楼1504室
  • 邮编:100085
  • 电话:010-57116291 / 59813326
  • 传真:010-59813329
  • 网址:http://www.kongzhi.net
  • Email:mahongliang@kongzhi.net
  • 联系人:市场部
案例详细
标题一种基于Gcc的控制编程语言编译方案
技术领域仪器仪表
行业
简介
内容
 
    胡志远(1982-)
男,信息产业部电子六所硕士研究生,(华北计算机系统工程研究所,北京  100083),计算机应用技术专业,在读。现于北京和利时系统工程公司实习,研究方向为工业控制语言编译。

摘要:
Gcc是公认的功能强大的开源编译器。本文介绍了一种利用Gcc实现控制器编程语言编译功能的方案。

关键词:
Gcc;编译

Abstract:
It is well-known that Gcc(Gnu C Compiler)is a powerful “Open Source” compiler. In this paper, we introduce the method to compile the control languages using Gcc.

Key words:
  Gcc;compile

1  引言

    用控制编程语言编写的算法经过编译后,通常都会下装到控制器中由控制器软件调度执行,而不是由操作系统直接调度执行。因此,对编译生成的二进制格式有特殊要求。本文将介绍一种利用Gcc编译器辅助生成项目所需的特殊格式二进制目标文件的方案。

2  概述

  整体编译方案如图1所示。



图 1  组态整体编译方案图

    首先,控制语言经过编译产生逻辑上等价的C代码,然后,编译模块对C文件进行编译处理生成所需特殊格式目标文件。

    编译模块会借助Gcc工具链对C文件进行编译连接等处理。实现方案中关键部分有:

    (1)通过修改汇编指令BL,实现函数间接地址跳转。

    (2)通过使用Gcc的扩展语法__attribute__,辅助链接脚本控制链接过程,利用Gcc工具链中链接器ld对.o文件进行链接实现变量指定地址分配。

    (3)通过使用Gcc工具链中objdump工具,取得.o文件中重定位信息。

    (4)通过使用Gcc工具链中readelf工具,提取.l文件中机器码信息以及部分重定位信息。

3  关键技术介绍



图 2   编译模块处理流程

    上图展示了编译模块内部处理方案。下面以图2中所示处理流程为序,介绍编译过程中的关键技术。

3.1  修改函数调用指令

    编译特殊需求:函数调用方式要求为间接地址调用。

    Gcc编译C代码生成的汇编文件中函数调用采用的是相对地址偏移跳转方式。ARM汇编中,BL指令为函数调用指令,为实现函数调用方式改变,我们必须对所有BL指令进行修改。在汇编这个环节进行修改,而不是.o文件,因为.o文件修改可能会破坏相对地址跳转类指令,例如B指令。

    例如:某段C代码编译产生的汇编文件中可能出现下面的函数调用语句

    bl    Fun_No1

    这种函数调用形式实际上是相对地址偏移跳转,不满足编译模块对函数调用方式的特殊需求,为了实现函数间接地址跳转,对函数调用汇编语句进行修改模拟BL指令动作,修改后的汇编形式如下:

       b     .$L0
.$L1:
       .word      52
.$L0:
       ldr   r8,.$L1
       ldr   r8,[r8]
       mov lr,pc
       mov pc,r8

    .word      52为Fun_No1的函数指针地址,该地址需要进行重定位,此处重定位信息提取将在提取机器码部分介绍。
 两条ldr指令将指针指向地址取出,然后赋给pc实现函数间接地址调用。其中标号.$L0中使用符号$可以保证标号的唯一性,C语言中标示符中不可能出现$符号。

    说明: BL(Branch and Link)指令,该指令执行引起地址跳转到一个目的地址,并且将函数返回地址存储到LR寄存器中;LR寄存器,即Link Register(R14),该寄存器保存BL指令下一条指令的地址,函数从子例程返回后从该地址开始继续执行;PC寄存器,即Program Counter,可以在大多数指令中做为一个指令指针,总是指向当前执行指令后的第二条指令。所有的ARM指令均四字节长。并且总是对齐到四字节边界,所以PC中最低两位总是0。[1]

3.2 提取重定位信息

    编译特殊需求:搜集目标代码中的重定位信息。

    编译模块生成的目标代码下装到控制器后需要重定位才能进行IEC运算,由于在链接后,Gcc在反编译目标代码已经无法提取重定位信息,所以要在链接前提取重定位信息。在连接的过程中,目标代码的长度和执行顺序都没有变化,只是操作变量或指针的地址进行了重新填充,所以在连接前提取的重定位信息对链接后的代码同样适用。

    在链接前,通过调用Gcc工具链中的反编译程序objdump,以-r作为命令行参数可得到当前代码中需要进行重定位的位置信息。即以”objdump -r *.o -o *.r”为参数启用新进程执行反编译程序objdump,生成包含重定位信息的.r文件。整理.r文件中所有的重定位信息得到最终目标文件中的一部分重定位数据。另外一部分重定位信息在提取机器码阶段获得,在后面内容中会介绍。下面举例说明.r文件中的重定位信息:

    执行命令objdump –r Test.o结果如下:

Test.o:     file format elf32-bigarm
 
RELOCATION RECORDS FOR [.text]:
OFFSET        TYPE                    VALUE
00000030      R_ARM_ABS32    AT_VAR
00000064      R_ARM_PC24      .text
... ...

    说明:由于AutoThinker对应的控制器程序不支持代码段重定位,只支持数据段的重定位,所以在提取重定位信息的时候,只需要提取数据段重定位数据。例子中TYPE为R_ARM_ABS32的行记录了数据段重定位信息,该行中OFFSET项表明Test.o文件中代码段偏移0x00000030处开始的四字节数据需要进行重定位处理。

3.3 链接

    编译模块特殊需求:实现变量指定地址分配。

    简单的讲,链接器的工作就是解析未定义的符号引用,将目标文件(这里指.o文件)中的占位符替换为符号的地址。目标文件是包括机器码和链接器可用信息的程序模块。链接器将完成程序中各目标文件的地址空间的组织。

    每个链接都被一个链接脚本所控制,这个脚本是用链接命令语言书写的。

    链接脚本的一个主要目的是描述输入文件中的节如何被映射到输出文件中,并控制输出文件的内存排布。几乎所有的链接脚本只做这两件事情。但是,在需要的时候, 链接脚本还可以指示链接器执行很多其他的操作。[2]

    链接器一般都有自己默认的链接脚本,要控制链接器的行为,可以过使用'-T'命令行选项来提供自己的链接脚本。

    使用'SECTIONS'命令来描述输出文件的内存布局。下面内容摘自我们编写的链接脚本myld.lds。

SECTIONS
{     
           . = 0x3008;
           .data : { *(.data) }
           MySection  0x00203008 : { *(MySection)}
              … …
. = 0x0100000;
           .text ALIGN(0x00100000) : { *(.text) }
}

    说明:脚本中命令指示,data段载入到地址0x3008处,text段载入到地址0x100000处,脚本中MySection为我们自己定义的段,对应于C代码语句:

    AT_VAR_T  Var_Test  __attribute__((section("MySection")));[3]

    AT_VAR_T类型变量Var_Test将载入到地址0x00203008开始处。__attribute__为Gcc语法扩展。一般情况下编译器将数据存放到data段或bss段中,当我们有特殊需求,需要将某些变量放到特定段中的时候可以使用Gcc__attribute__扩展语法。上面的C语句定义了新的段MySection,配合链接脚本myld.lds的定位功能我们就实现了变量的指定地址分配。

    执行链接操作的命令如下:

    ld –T myld.lds Test.o –o Test.l

    ld使用myld.lds作为链接脚本,将Test.o文件进行链接,生成文件Test.l。

3.4  提取机器码

    编译特殊需求:以函数为单位提取出编译生成得二进制机器码。

    借助Gcc工具链中的readelf工具,我们可以从链接后的.l文件中提取每个函数对应的二进制机器码。

    执行命令readelf -S -s Test.l结果如下:

Section Headers:
  [Nr] Name         Type              Addr              Off         Size        ES   Flg Lk Inf Al
  … …
  [ 5] .text           PROGBITS    00100000       008000    000070    00  AX  0   0  4
  … …
 
Symbol table '.symtab' contains 22 entries:
   Num:    Value  Size   Type              Bind   Vis                  Ndx Name
       … …
    18:  00100044       0     NOTYPE  LOCAL  DEFAULT    8       .$L1
… …
    21: 0010005c         20    FUNC    GLOBAL     DEFAULT    5    main

    Section Headers部分第五行显示.text段Addr属性为0x00100000,该值与链接脚本中text段载入地址一致;.text段Off属性为0x008000,该值表明Test.l中偏移0x8000处为text段内容。

    Symbol table部分第21行内容表示,main函数载入地址0x10005c,函数长度20。结合Section Header中获得信息,我们可以知道Test.l文件中偏移0x805c(计算公式:函数载入地址-text段载入地址+text段在.l文件中的偏移。即,0x10005c - 0x100000 + 0x8000)处开始20个字节内容为main函数函数体对应的机器码。

    Symbol Table部分第18行内容表示C代码中标号$L1对应机器码的载入地址为0x100044,结合Section Headers中信息,可以知道该标号相对text段的偏移为0x44,该标号指示位置需要进行重定位。此部分重定位信息与objdump提取的重定位信息相结合就得到了我们所需要的全部重定位信息。

4  结束语

    在嵌入式应用中,往往对编译功能有种种的特殊需求。通过运用Gcc工具链中的各种编译工具,我们可以定制程序编译过程,以满足特殊的编译需求。

参考文献

    [1]      ARM Limited.  ARM Architecture Reference Manual. June 2000.

    [2]      Free Software Foundation, Inc.ld.info. 2004.

    [3]      Richard M. Stallman and the GCC Developer Community.Using the GNU Compiler Collection. 2005.