
男,信息產業(yè)部電子六所碩士研究生,(華北計算機系統工程研究所,北京 100083),計算機應用技術專業(yè),在讀。現于北京和利時系統工程公司實習,研究方向為工業(yè)控制語言編譯。
摘要: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 引言
用控制編程語言編寫的算法經過編譯后,通常都會下裝到控制器中由控制器軟件調度執(zhí)行,而不是由操作系統直接調度執(zhí)行。因此,對編譯生成的二進制格式有特殊要求。本文將介紹一種利用Gcc編譯器輔助生成項目所需的特殊格式二進制目標文件的方案。
2 概述
整體編譯方案如圖1所示。
圖 1 組態(tài)整體編譯方案圖
首先,控制語言經過編譯產生邏輯上等價的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指令進行修改。在匯編這個環(huán)節(jié)進行修改,而不是.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)指令,該指令執(zhí)行引起地址跳轉到一個目的地址,并且將函數返回地址存儲到LR寄存器中;LR寄存器,即Link Register(R14),該寄存器保存BL指令下一條指令的地址,函數從子例程返回后從該地址開始繼續(xù)執(zhí)行;PC寄存器,即Program Counter,可以在大多數指令中做為一個指令指針,總是指向當前執(zhí)行指令后的第二條指令。所有的ARM指令均四字節(jié)長。并且總是對齊到四字節(jié)邊界,所以PC中最低兩位總是0。[1]
3.2 提取重定位信息
編譯特殊需求:搜集目標代碼中的重定位信息。
編譯模塊生成的目標代碼下裝到控制器后需要重定位才能進行IEC運算,由于在鏈接后,Gcc在反編譯目標代碼已經無法提取重定位信息,所以要在鏈接前提取重定位信息。在連接的過程中,目標代碼的長度和執(zhí)行順序都沒有變化,只是操作變量或指針的地址進行了重新填充,所以在連接前提取的重定位信息對鏈接后的代碼同樣適用。
在鏈接前,通過調用Gcc工具鏈中的反編譯程序objdump,以-r作為命令行參數可得到當前代碼中需要進行重定位的位置信息。即以”objdump -r *.o -o *.r”為參數啟用新進程執(zhí)行反編譯程序objdump,生成包含重定位信息的.r文件。整理.r文件中所有的重定位信息得到最終目標文件中的一部分重定位數據。另外一部分重定位信息在提取機器碼階段獲得,在后面內容中會介紹。下面舉例說明.r文件中的重定位信息:
執(zhí)行命令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處開始的四字節(jié)數據需要進行重定位處理。
3.3 鏈接
編譯模塊特殊需求:實現變量指定地址分配。
簡單的講,鏈接器的工作就是解析未定義的符號引用,將目標文件(這里指.o文件)中的占位符替換為符號的地址。目標文件是包括機器碼和鏈接器可用信息的程序模塊。鏈接器將完成程序中各目標文件的地址空間的組織。
每個鏈接都被一個鏈接腳本所控制,這個腳本是用鏈接命令語言書寫的。
鏈接腳本的一個主要目的是描述輸入文件中的節(jié)如何被映射到輸出文件中,并控制輸出文件的內存排布。幾乎所有的鏈接腳本只做這兩件事情。但是,在需要的時候, 鏈接腳本還可以指示鏈接器執(zhí)行很多其他的操作。[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的定位功能我們就實現了變量的指定地址分配。
執(zhí)行鏈接操作的命令如下:
ld –T myld.lds Test.o –o Test.l
ld使用myld.lds作為鏈接腳本,將Test.o文件進行鏈接,生成文件Test.l。
3.4 提取機器碼
編譯特殊需求:以函數為單位提取出編譯生成得二進制機器碼。
借助Gcc工具鏈中的readelf工具,我們可以從鏈接后的.l文件中提取每個函數對應的二進制機器碼。
執(zhí)行命令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個字節(jié)內容為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.