MDK編譯過程及其中間檔案介紹
整個工作過程
-
編譯:
編譯器是armcc(C檔案程式碼)和armasm(彙編檔案程式碼),它們根據每個c/c++和彙編原始檔編譯成對應的以".o"為字尾名的物件檔案(Object Code,也稱目標檔案),其內容主要是從原始檔編譯得到的機器碼,包含了程式碼、資料以及除錯使用的資訊。但是其中不包含 地址資訊 -
連結:
連結器armlink把各個.o檔案及庫檔案根據你在MDK中的晶片選型 地址資訊設定 連結成一個映像檔案".axf"或".elf"。這個".axf"檔案是包含地址資訊的。其中還會提示程式儲存空間分配,這個具體下面再講
Program Size: Code=14636 RO-data=576 RW-data=204 ZI-data=2732
- 格式轉換:
一般來說Windows或Linux系統使用連結器直接生成可執行映像檔案elf後,核心根據該檔案的資訊載入後,就可以執行程式了。但在微控制器平臺上,需要把該檔案的內容載入到晶片上,所以還需要對連結器生成的elf映像檔案利用格式轉換器fromelf轉換成“.bin”或“.hex”檔案,交給下載器下載到晶片的FLASH或ROM中。
程式儲存空間分配
應用程式中所有具有同一性質的資料(包括程式碼)被歸到一個域,程式在儲存或執行的時候,不同的域儲存、讀寫屬性各不相同。主要分為幾大類CODE、 RO、 RW、 ZI Data。在《程式設計師的自我修養》這本書裡面講了很多這種編譯、連結類的知識。下面僅僅是針對於MCU的架構,指的是nor flash+sram的結構
-
CODE:程式碼段,這個是純粹的程式碼,MCU就是執行這些二進位制指令。在儲存時程式碼段存放在nor flash中,在執行時程式碼段仍然在nor flash中,這跟PC等架構不一樣。MCU核心直接從nor flash取指執行,不需要將CODE載入到RAM中
-
RO:只讀資料區,指程式中用到的只讀資料,這些資料被儲存在ROM區,因而程式不能修改其內容。在儲存時只讀資料區在flash中,在執行時仍然在flash中,並不會被載入到sram中。比如
char *p="abc";
,其實abc字串是存放在rom區的。一般情況下const定義的變數也是屬於只讀的存放在rom flash中。 -
RW:可讀可寫資料段,它指初始化為“非0值”的可讀寫資料,程式剛執行時,這些資料具有非0的初始值,且執行的時候它們會常駐在RAM區,因而應用程式可以修改其內容。RW段在儲存時放在flash中,在執行時被載入到sram中。如定義的全域性變數(不初始化為0的)。
-
ZI Data:一般又稱為BSS段。即0初始化資料(Zero Initialied Data),它指初始化為“0值”的可讀寫資料域,它與RW-data的區別是程式剛執行時這些資料初始值全都為0,而後續執行過程與RW-data的性質一樣,它們也常駐在RAM區,因而應用程式可以更改其內容。在儲存的時候並不佔用flash中的資源,因為反正都是0,只需要記錄有多少個0即可,在執行的時候會在SRAM中分配相應的大小的記憶體。
-
堆疊段:在儲存的時候是不佔用空間的(或者說就沒有儲存),在執行的時候有著自己的一套規則。
程式狀態與區域 | 組成 |
---|---|
程式執行時的只讀區域(RO) | Code + RO data |
程式執行時的可讀寫區域(RW) | RW data + ZI data |
程式儲存時佔用的ROM區 | Code + RO data + RW data |
MDK工具鏈
MDK就是將一系列工具如armcc、armlink、armar、 fromelf整合並做了友好的GUI介面,實際幹活的還是這些軟體工具。
下面介紹這些軟體跟MDK設定介面的關係
首先新增環境變數,方便後續操作:如果是按MDK預設安裝路徑來的話,需要將fromelf的路徑加入到環境變數,即C:\Keil_v5\ARM\ARMCC\bin
加入環境變數。
-
armcc :就是實際幹活的編譯器
在cmd視窗中輸入armcc會提示一串用法,在MDK的Options For Targets->C/C+±>Compiler control string 視窗的一串字串其實就是armcc編譯時的選項,只不過這些選項是由MDK內建指令碼自動生成的而已。
輸入armcc --cpu list
可以看到當前版本armcc支援哪些核心的CPU -
armlink :就是實際幹活的編譯器
在cmd視窗中輸入armlink會提示一串用法,在MDK的Options For Targets->Linker>Linker control string視窗的一串字串其實就是armlink連結時的選項,只不過這些選項是由MDK內建指令碼自動生成的而已。
連結器根據晶片的地址資訊 和 armcc生成的.o檔案來生成elf格式的axf可執行檔案。
那麼這個晶片的地址資訊是哪裡來的?原來在我們給晶片選型之後,MDK自動生成了一個.sct檔案,這個也叫載入檔案,連結器也就是根據.sct檔案來確定連結地址的,不同晶片選型會生成不同的.sct檔案。還有一篇文章將分散載入的部落格,其中主要就是講.sct檔案的。 -
armar:是用於將工程檔案打包成庫檔案的一個工具
在MDK中有Options for Targets->Output->Create Library選項,就是利用armar來生成庫檔案的。在你不想給對方原始碼,只想提供API介面的時候可以使用這種方法。 -
fromelf:可以根據elf格式的axf檔案生成HEX、BIN檔案
但是僅僅集成了hex選項,可以在Options for Targets->Output中看到。如果你想生成BIN檔案怎麼操作呢?當然第一種方法你可以在cmd命令列中根據已經生成的.axf檔案利用fromelf工具來生成BIN檔案;第二種方法是MDK中有Options for Targets->User視窗,裡面就是讓使用者根據編譯過程來新增自己的指令碼命令的,其中分為了三個階段,編譯前、連結前、連結後,如果我們想利用.axf檔案僅僅需要在連線後的裡面新增指令碼fromelf.exe --bin -o ..\OBJ\LED.bin ..\OBJ\LED.axf
即可
MDK工程檔案型別詳解
字尾 | 檔案型別 |
---|---|
*.lib | 庫檔案 |
*.dep | 整個工程的依賴檔案 |
*.d | 描述了對應.o的依賴的檔案 |
*.crf | 交叉引用檔案, 包含了瀏覽資訊(定義、 引用及識別符號) |
*.o | 可重定位的物件檔案(目標檔案) |
*.bin | 二進位制格式的映像檔案, 是純粹的FLASH映像, 不含任何額外資訊 |
*.hex | Intel Hex格式的映像檔案, 可理解為帶儲存地址描述格式的bin檔案 |
*.elf | 由GCC編譯生成的檔案, 功能跟axf檔案一樣, 該檔案不可重定位 |
*.axf | 由ARMCC編譯生成的可執行物件檔案, 可用於除錯, 該檔案不可重定位 |
*.sct | 連結器控制檔案(分散載入) |
*.scr | 連結器產生的分散載入檔案 |
*.lnp | MDK生成的連結輸入檔案, 用於呼叫連結器時的命令輸入 |
*.htm | 連結器生成的靜態呼叫圖檔案 |
*.build_log.htm | 構建工程的日誌記錄檔案 |
*.lst | C及彙編編譯器產生的列表檔案 |
*.map | 連結器生成的列表檔案, 包含儲存器映像分佈 |
*.ini | 模擬、 下載器的指令碼檔案 |
*.uvprojx | 記錄了整個工程的結構,如晶片型別、工程包含了哪些原始檔等內容 |
*.uvoptx | 記錄了工程的配置選項,如下載器的型別、變數跟蹤配置、斷點位置以及當前已開啟的檔案 |
*.uvguix | 記錄了MDK軟體的GUI佈局,如程式碼編輯區視窗的大小、編譯輸出提示視窗的位置 |
HEX與BIN區別和聯絡
HEX檔案是包括地址資訊的,HEX檔案都是由記錄(RECORD)組成的。在HEX檔案裡面,每一行代表一個記錄,而BIN檔案格式只包括了資料本身,純粹的二進位制資料連大小端都沒有,任何輔助資訊都沒有。HEX檔案是用ASCII來表示二進位制的數值。例如一般8-BIT的二進位制數值0x5E,用ASCII來表示就需要分別表示字元’5’和字元’E’,每個字元需要一個BYTE,所以HEX檔案需要 > 2倍的空間。對一個BIN檔案而言,你檢視檔案的大小就可以知道檔案包括的資料的實際大小。而對HEX檔案而言,你看到的檔案 大小並不是實際的資料的大小。一是因為HEX檔案是用ASCII來表示資料,二是因為HEX檔案本身還包括別的附加資訊,如地址資訊。
在燒寫或下載HEX檔案的時候,一般都不需要使用者指定地址,因為HEX檔案內部的資訊已經包括了地址。而燒寫BIN檔案的時候,使用者是一定需要指定地址資訊的(MDK下載的就是HEX檔案,ESP8266自己編譯韌體產生bin檔案下載的時候需要指定flash地址)。看圖
MDK截圖
Eclipse 和 ESP8266下載工具截圖
HEX檔案詳解
下面是一個hex檔案前幾行
:020000040800F2
:10000000780B0020E1080008C9020008AB150008C1
:10001000CB020008CF020008D30200080000000055
:10002000000000000000000000000000D7020008EF
:10003000D90200080000000033150008AB360008A4
:10004000FB080008FB080008FB080008FB08000884
:10005000FB080008FB080008FB080008FB08000874
:10006000FB080008FB080008FB080008FB08000864
每行是以:開始,代表一條記錄,一條記錄的基本格式是:llaaaatt[dd…]cc
- : :冒號是一條記錄開始
- ll:以16進位制數表示這條記錄的主體資料區的長度
- aaaa:表示這條記錄中的內容應存放到FLASH中的起始地址
- tt:表示這條記錄的型別,它包含中的各種型別
tt | 資料型別 |
---|---|
00 | 資料記錄 |
01 | 本檔案結束記錄 |
02 | 擴充套件地址記錄 |
04 | 擴充套件線性地址記錄(表示後面的記錄按個這地址遞增) |
05 | 表示一個線性地址記錄的起始(只適用於ARM) |
- dd:表示一個位元組的資料,一條記錄中可以有多個位元組資料
- cc:表示本條記錄的校驗和,它是前面所有16進位制資料 (除冒號外,兩個為一組)的和對256取模運算的結果的補碼