MDK的編譯過程及文件類型全解——(一)

前言:

爲了方便查看博客,特意申請了一個公衆號,附上二維碼,有興趣的朋友可以關注,和我一起討論學習,一起享受技術,一起成長。

在這裏插入圖片描述


本文轉載自:第48章 MDK的編譯過程及文件類型全解—零死角玩轉STM32-F429系列


1. 簡介

本章參考資料:MDK 的幫助手冊《ARM Development Tools》,點擊 MDK 界面的 “help->uVision Help” 菜單可打開該文件。關於 ELF 文件格式,參考配套資料裏的《ELF文件格式》文件。

在本章中講解了非常多的文件類型,學習時請跟着教程的節奏,打開實際工程中的文件來了解。

相信您已經非常熟練地使用 MDK 創建應用程序了,平時使用 MDK 編寫源代碼,然後編譯生成機器碼,再把機器碼下載到 STM32 芯片上運行,但是這個編譯、下載的過程 MDK 究竟做了什麼工作?它編譯後生成的各種文件又有什麼作用?本章節將對這些過程進行講解,瞭解編譯及下載過程有助於理解芯片的工作原理,這些知識對製作 IAP(bootloader) 以及讀寫控制器內部 FLASH 的應用時非常重要。

2. 編譯過程

2.1 編譯過程簡介

首先我們簡單瞭解下 MDK 的編譯過程,它與其它編譯器的工作過程是類似的,該過程見下圖。

在這裏插入圖片描述
編譯過程生成流程:

(1) 編譯:MDK 軟件使用的編譯器是 armcc 和 armasm,它們根據每個 c/c++ 和彙編源文件編譯成對應的以 “.o” 爲後綴名的對象文件 (Object Code,也稱目標文件),其內容主要是從源文件編譯得到的機器碼,包含了代碼、數據以及調試使用的信息;

(2) 鏈接:鏈接器 armlink 把各個 .o 文件及庫文件鏈接成一個映像文件 “.axf” 或 “.elf”;

(3) 格式轉換:一般來說 Windows 或 Linux 系統使用鏈接器直接生成可執行映像文件 elf 後,內核根據該文件的信息加載後,就可以運行程序了,但在單片機平臺上,需要把該文件的內容加載到芯片上,所以還需要對鏈接器生成的 elf 映像文件利用格式轉換器 fromelf 轉換成 “.bin” 或 “.hex” 文件,交給下載器下載到芯片的FLASH 或 ROM 中。

2.2 具體工程中的編譯過程

下面我們打開"多彩流水燈"的工程,以它爲例進行講解,其它工程的編譯過程也是一樣的,只是文件有差異。打開工程後,點擊 MDK 的 “rebuild” 按鈕,它會重新構建整個工程,構建的過程會在 MDK 下方的 “Build Output” 窗口輸出提示信息,見下圖。

在這裏插入圖片描述
構建工程的提示輸出主要分 6 個部分,說明如下:

(1) 提示信息的第一部分說明構建過程調用的編譯器。圖中的編譯器名字是 “V5.06(build 20)”,後面附帶了該編譯器所在的文件夾。在電腦上打開該路徑,可看到該編譯器包含上圖中的各個編譯工具,如 armar、armasm、armcc、armlink及 fromelf。armar 是用於把 .o 文件打包成 lib 文件的。

在這裏插入圖片描述(2)使用 armasm 編譯彙編文件。圖中列出了編譯 startup 啓動文件時的提示,編譯後每個彙編源文件都對應有一個獨立的 .o 文件。

(3)使用 armcc 編譯 c/c++ 文件。圖中列出了工程中所有的 c/c++ 文件的提示,同樣地,編譯後每個 c/c++ 源文件都對應有一個獨立的 .o 文件。

(4) 使用 armlink 鏈接對象文件,根據程序的調用把各個 .o 文件的內容鏈接起來,最後生成程序的 axf 映像文件,並附帶程序各個域大小的說明,包括 Code、RO-data、RW-data 及 ZI-data 的大小。

(5)使用 fromelf 生成下載格式文件,它根據 axf 映像文件轉化成 hex 文件,並列出編譯過程出現的錯誤 (Error) 和警告 (Warning) 數量。

(6) 最後一段提示給出了整個構建過程消耗的時間。

構建完成後,可在工程的 “Output” 及 “Listing” 目錄下找到由以上過程生成的各種文件,見下圖。

在這裏插入圖片描述

可以看到,每個 C 源文件都對應生成了 .o、.d 及 .crf 後綴的文件,還有一些額外的 .dep、.hex、.axf、.htm、.lnp、.sct、.lst 及 .map 文件。

3. 程序的組成、存儲與運行

3.1 CODE、RO、RW、ZI Data域及堆棧空間

在工程的編譯提示輸出信息中有一個語句 “Program Size:Code=xx RO-data=xx RW-data=xx ZI-data=xx”,它說明了程序各個域的大小,編譯後,應用程序中所有具有同一性質的數據(包括代碼)被歸到一個域,程序在存儲或運行的時候,不同的域會呈現不同的狀態,這些域的意義如下:

(1) Code:即代碼域,它指的是編譯器生成的機器指令,這些內容被存儲到 ROM 區。

(2)RO-data:Read Only data,即只讀數據域,它指程序中用到的只讀數據,這些數據被存儲在 ROM 區,因而程序不能修改其內容。例如 C 語言中 const 關鍵字定義的變量就是典型的 RO-data。

(3)RW-data:Read Write data,即可讀寫數據域,它指初始化爲"非 0 值"的可讀寫數據,程序剛運行時,這些數據具有非 0 的初始值,且運行的時候它們會常駐在 RAM 區,因而應用程序可以修改其內容。例如 C 語言中使用定義的全局變量,且定義時賦予"非 0 值"給該變量進行初始化。

(4)ZI-data:Zero Initialie data,即 0 初始化數據,它指初始化爲 " 0 值" 的可讀寫數據域,它與 RW-data 的區別是程序剛運行時這些數據初始值全都爲 0,而後續運行過程與 RW-data 的性質一樣,它們也常駐在 RAM 區,因而應用程序可以更改其內容。例如 C 語言中使用定義的全局變量,且定義時賦予 “0 值” 給該變量進行初始化(若定義該變量時沒有賦予初始值,編譯器會把它當 ZI-data 來對待,初始化爲 0);

(5)ZI-data 的棧空間 (Stack) 及堆空間 (Heap):在 C 語言中,函數內部定義的局部變量屬於棧空間,進入函數的時候從向棧空間申請內存給局部變量,退出時釋放局部變量,歸還內存空間。而使用 malloc 動態分配的變量屬於堆空間。在程序中的棧空間和堆空間都是屬於 ZI-data 區域的,這些空間都會被初始值化爲 0 值。編譯器給出的 ZI-data 佔用的空間值中包含了堆棧的大小(經實際測試,若程序中完全沒有使用 malloc 動態申請堆空間,編譯器會優化,不把堆空間計算在內)。

綜上所述,以程序的組成構件爲例,它們所屬的區域類別見下表:

在這裏插入圖片描述

3.2 程序的存儲與運行

RW-data 和 ZI-data 它們僅僅是初始值不一樣而已,爲什麼編譯器非要把它們區分開?這就涉及到程序的存儲狀態了,應用程序具有靜止狀態和運行狀態。靜止狀態的程序被存儲在非易失存儲器中,如 STM32 的內部 FLASH,因而系統掉電後也能正常保存。但是當程序在運行狀態的時候,程序常常需要修改一些暫存數據,由於運行速度的要求,這些數據往往存放在內存中 (RAM),掉電後這些數據會丟失。因此,程序在靜止與運行的時候它在存儲器中的表現是不一樣的,見下圖 。

在這裏插入圖片描述圖中的左側是應用程序的存儲狀態,右側是運行狀態,而上方是 RAM 存儲器區域,下方是R OM 存儲器區域。

程序在存儲狀態時,RO 節 (RO section) 及 RW 節都被保存在 ROM 區。當程序開始運行時,內核直接從 ROM 中讀取代碼,並且在執行主體代碼前,會先執行一段加載代碼,它把 RW 節數據從 ROM 複製到 RAM,並且在 RAM 加入 ZI 節,ZI 節的數據都被初始化爲 0。加載完後 RAM 區準備完畢,正式開始執行主體程序。

編譯生成的 RW-data 的數據屬於圖中的 RW 節,ZI-data 的數據屬於圖中的 ZI 節。是否需要掉電保存,這就是把 RW-data 與 ZI-data 區別開來的原因,因爲在RAM 創建數據的時候,默認值爲 0,但如果有的數據要求初值非 0,那就需要使用 ROM 記錄該初始值,運行時再複製到 RAM。

STM32 的 RO 區域不需要加載到 SRAM,內核直接從 FLASH 讀取指令運行。計算機系統的應用程序運行過程很類似,不過計算機系統的程序在存儲狀態時位於硬盤,執行的時候甚至會把上述的 RO 區域(代碼、只讀數據)加載到內存,加快運行速度,還有虛擬內存管理單元 (MMU) 輔助加載數據,使得可以運行比物理內存還大的應用程序。而 STM32 沒有 MMU,所以無法支持 Linux 和 Windows 系統。

當程序存儲到 STM32 芯片的內部 FLASH 時(即 ROM 區),它佔用的空間是 Code、RO-data 及 RW-data 的總和,所以如果這些內容比 STM32 芯片的 FLASH 空間大,程序就無法被正常保存了。當程序在執行的時候,需要佔用內部 SRAM 空間(即 RAM 區),佔用的空間包括 RW-data 和 ZI-data。應用程序在各個狀態時各區域的組成見下表。

在這裏插入圖片描述
在 MDK 中,我們建立的工程一般會選擇芯片型號,選擇後就有確定的 FLASH 及 SRAM 大小,若代碼超出了芯片的存儲器的極限,編譯器會提示錯誤,這時就需要裁剪程序了,裁剪時可針對超出的區域來優化。

4. 編譯工具鏈

在前面編譯過程中,MDK 調用了各種編譯工具,平時我們直接配置 MDK,不需要學習如何使用它們,但瞭解它們是非常有好處的。例如,若希望使用 MDK 編譯生成 bin 文件的,需要在 MDK 中輸入指令控制 fromelf 工具;在本章後面講解 AXF 及 .O 文件的時候,需要利用 fromelf 工具查看其文件信息,這都是無法直接通過 MDK 做到的。關於這些工具鏈的說明,在 MDK 的幫助手冊《ARM Development Tools》都有詳細講解,點擊 MDK 界面的 “help->uVision Help” 菜單可打開該文件。

4.1 設置環境變量

調用這些編譯工具,需要用到 Windows 的"命令行提示符工具",爲了讓命令行方便地找到這些工具,我們先把工具鏈的目錄添加到系統的環境變量中。查看本機工具鏈所在的具體目錄可根據上一小節講解的工程編譯提示輸出信息中找到,如本機的路徑爲 “D:\work\keil5\ARM\ARMCC\bin”。

4.1.1 添加路徑到 PATH 環境變量

本文以 Win7 系統爲例添加工具鏈的路徑到 PATH 環境變量,其它系統是類似的。

(1) 右鍵電腦系統的"計算機圖標",在彈出的菜單中選擇"屬性",見下圖:

在這裏插入圖片描述(2) 在彈出的屬性頁面依次點擊"高級系統設置 “->” 環境變量",在用戶變量一欄中找到名爲 “PATH” 的變量,若沒有該變量,則新建一個。編輯 “PATH” 變量,在它的變量值中輸入工具鏈的路徑,如本機的是 “;D:\work\keil5\ARM\ARMCC\bin”,注意要使用"分號 “;” 讓它與其它路徑分隔開,輸入完畢後依次點確定,見下圖 ;

在這裏插入圖片描述
(3) 打開 Windows 的命令行,點擊系統的"開始菜單",在搜索框輸入 “cmd”,在搜索結果中點擊 “cmd.exe” 即可打開命令行,見下圖;

在這裏插入圖片描述
(4) 在彈出的命令行窗口中輸入 “fromelf” 回車,若窗口打印出 formelf 的幫助說明,那麼路徑正常,就可以開始後面的工作了;若提示"不是內部名外部命令,也不是可運行的程序…"信息,說明路徑不對,請重新配置環境變量,並確認該工作目錄下有編譯工具鏈。

這個過程本質就是讓命令行通過 “PATH” 路徑找到 “fromelf.exe” 程序運行,默認運行 “fromelf.exe” 時它會輸出自己的幫助信息,這就是工具鏈的調用過程,MDK 本質上也是如此調用工具鏈的,只是它集成爲 GUI,相對於命令行對用戶更友好,畢竟上述配置環境變量的過程已經讓新手煩躁了。

4.2 armcc、armasm及armlink

接下來我們看看各個工具鏈的具體用法,主要以 armcc 爲例。

(1)armcc

armcc 用於把 c/c++ 文件編譯成 ARM 指令代碼,編譯後會輸出 ELF 格式的 .O 文件(對象、目標文件),在命令行中輸入 “armcc” 回車可調用該工具,它會打印幫助說明,見下圖。

在這裏插入圖片描述幫助提示中分三部分,第一部分是 armcc 版本信息,第二部分是命令的用法,第三部分是主要命令選項。

根據命令用法: armcc [options] file1 file2 … filen ,在 [option] 位置可輸入下面的 “–arm”、"–cpu list" 選項,若選項帶文件輸入,則把文件名填充在 file1 file2…的位置,這些文件一般是 c/c++ 文件。

根據它的幫助說明,"–cpu list" 可列出編譯器支持的所有 cpu,我們在命令行中輸入 “armcc --cpu list”,可查看下圖中的cpu列表。

在這裏插入圖片描述
打開 MDK 的 Options for Targe->c/c++ 菜單,可看到 MDK 對編譯器的控制命令,見下圖。

在這裏插入圖片描述從該圖中的命令可看到,它調用了 -c、-cpu –D –g –O0 等編譯選項,當我們修改 MDK 的編譯配置時,可看到該控制命令也會有相應的變化。然而我們無法在該編譯選項框中輸入命令,只能通過 MDK 提供的選項修改。

瞭解這些,我們就可以查詢具體的 MDK 編譯選項的具體信息了,如 c/c++ 選項中的 " Optimization:Leve 1(-O1)" 是什麼功能呢?首先可瞭解到它是 “-O” 命令,命令後還帶個數字,查看 MDK 的幫助手冊,在 armcc 編譯器說明章節,可詳細瞭解,如下圖。

在這裏插入圖片描述利用 MDK,我們一般不需要自己調用 armcc 工具,但經過這樣的過程我們就會對 MDK 有更深入的認識。

(2)armasm

armasm 是彙編器,它把彙編文件編譯成 .O 文件。與 armcc 類似,MDK 對 armasm 的調用選項可在 “Option for Target->Asm” 頁面進行配置,見下圖。

在這裏插入圖片描述
(3)armlink

armlink 是鏈接器,它把各個 .O 文件鏈接組合在一起生成 ELF 格式的 AXF 文件,AXF 文件是可執行的,下載器把該文件中的指令代碼下載到芯片後,該芯片就能運行程序了;利用 armlink 還可以控制程序存儲到指定的 ROM 或 RAM 地址。在 MDK 中可在 “Option for Target->Linker” 頁面配置 armlink 選項,見下圖 。

在這裏插入圖片描述
鏈接器默認是根據芯片類型的存儲器分佈來生成程序的,該存儲器分佈被記錄在工程裏的 sct 後綴的文件中,有特殊需要的話可自行編輯該文件,改變鏈接器的鏈接方式。

4.3 armar、fromelf及用戶指令

armar 工具用於把工程打包成庫文件,fromelf 可根據 axf 文件生成 hex、bin 文件,hex 和 bin 文件是大多數下載器支持的下載文件格式。

在 MDK 中,針對 armar 和 fromelf 工具的選項幾乎沒有,僅集成了生成 HEX 或 Lib 的選項,見下圖。

在這裏插入圖片描述例如如果我們想利用 fromelf 生成 bin 文件,可以在 MDK 的 “Option for Target->User” 頁中添加調用 fromelf 的指令,見下圖。

在這裏插入圖片描述在 User 配置頁面中,提供了三種類型的用戶指令輸入框,在不同組的框輸入指令,可控制指令的執行時間,分別是編譯前 (Before Compile c/c++ file)、構建前 (Before Build/Rebuild) 及構建後 (After Build/Rebuild) 執行。這些指令並沒有限制必須是 arm 的編譯工具鏈,例如如果您自己編寫了 python 腳本,也可以在這裏輸入用戶指令執行該腳本。

圖中的生成 bin 文件指令調用了 fromelf 工具,緊跟後面的是工具的選項及輸出文件名、輸入文件名。由於 fromelf 是根據 axf 文件生成 bin 的,而 axf 文件又是構建 (build) 工程後才生成,所以我們把該指令放到 “After Build/Rebuild” 一欄。

5.MDK工程的文件類型

除了上述編譯過程生成的文件,MDK 工程中還包含了各種各樣的文件,下面我們統一介紹,MDK 工程的常見文件類型見下表。

在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述這些文件主要分爲MDK相關文件、源文件以及編譯、鏈接器生成的文件。我們以"多彩流水燈"工程爲例講解各種文件的功能。(參看:MDK的編譯過程及文件類型全解——(二))


轉載自——第48章 MDK的編譯過程及文件類型全解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章