1. 程式人生 > >MDK 的編譯過程及檔案型別全解

MDK 的編譯過程及檔案型別全解

出處:MDK 的編譯過程及檔案型別全解

 

MDK 的編譯過程及檔案型別全解

------(在arm9的開發中,這些東西都是我們自己搞定的,但是在windows上,IDE幫我們做好了,瞭解這些對深入開發是很有幫助的,在有arm9開發的基礎上,下面的東西很容易理解,如果看不懂,證明你還沒有入門。下面的是從world複製過來的,格式和部落格不太相容,所有開始以字母q的,是world中的 □ 字元)

本章參考資料: MDK 的幫助手冊《ARM Development Tools》,點選 MDK 介面的
“help->uVision Help”選單可開啟該檔案。關於 ELF 檔案格式,參考配套資料裡的《ELF
檔案格式》檔案。
在本章中講解了非常多的檔案型別,學習時請跟著教程的節奏,開啟實際工程中的文
件來了解。
相信您已經非常熟練地使用 MDK 建立應用程式了,平時使用 MDK 編寫原始碼,然
後編譯生成機器碼,再把機器碼下載到 STM32 晶片上執行,但是這個編譯、下載的過程
MDK 究竟做了什麼工作?它編譯後生成的各種檔案又有什麼作用?本章節將對這些過程進
行講解,瞭解編譯及下載過程有助於理解晶片的工作原理,這些知識對製作 IAP(bootloader)
以及讀寫控制器內部 FLASH 的應用時非常重要。

 

 

編譯過程生成的不同檔案將在後面的小節詳細說明,此處先抓住主要流程來理解。
(1) 編譯, MDK 軟體使用的編譯器是 armcc 和 armasm,它們根據每個 c/c++和彙編原始檔
編譯成對應的以“.o”為字尾名的物件檔案(Object Code,也稱目標檔案),其內容主要
是從原始檔編譯得到的機器碼,包含了程式碼、資料以及除錯使用的資訊;
(2) 連結,連結器 armlink 把各個.o 檔案及庫檔案連結成一個映像檔案“.axf”或“.elf”;

(3) 格式轉換,一般來說 Windows 或 Linux 系統使用連結器直接生成可執行映像檔案 elf
後,核心根據該檔案的資訊載入後,就可以執行程式了,但在微控制器平臺上,需要把
該檔案的內容載入到晶片上,所以還需要對連結器生成的 elf 映像檔案利用格式轉換器
fromelf 轉換成“.bin”或“.hex”檔案,交給下載器下載到晶片的 FLASH 或 ROM 中。

具體工程中的編譯過程
下面我們開啟 “多彩流水燈”的工程,以它為例進行講解,其它工程的編譯過程也是
一樣的,只是檔案有差異。開啟工程後,點選 MDK 的“rebuild”按鈕,它會重新構建整
個工程,構建的過程會在 MDK 下方的“Build Output”視窗輸出提示資訊,見圖 48-2。

 

 

(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”目錄下找到由以上過程生成的各種
檔案,見圖 48-4。

 

 

可以看到,每個 C 原始檔都對應生成了.o、 .d 及.crf 字尾的檔案,還有一些額外
的.dep、 .hex、 .axf、 .htm、 .lnp、 .sct、 .lst 及.map 檔案。

程式的組成、儲存與執行
CODE、 RO、 RW、 ZI Data 域及堆疊空間
在工程的編譯提示輸出資訊中有一個語句“Program Size: Code=xx RO-data=xx RWdata=xx ZI-data=xx”,它說明了程式各個域的大小,編譯後,應用程式中所有具有同一性質的資料(包括程式碼)被歸到一個域,程式在儲存或執行的時候,不同的域會呈現不同的狀
態,這些域的意義如下:
q Code:即程式碼域,它指的是編譯器生成的機器指令,這些內容被儲存到 ROM 區。
q RO-data: Read Only data,即只讀資料域,它指程式中用到的只讀資料,這些數
據被儲存在 ROM 區,因而程式不能修改其內容。例如 C 語言中 const 關鍵字定義
的變數就是典型的 RO-data(注:C語言中,const修飾的變數還是可以通過指標更改,c++中是不允許的)。
q RW-data: Read Write data,即可讀寫資料域,它指初始化為“非 0 值”的可讀寫
資料,程式剛執行時,這些資料具有非 0 的初始值,且執行的時候它們會常駐在
RAM 區,因而應用程式可以修改其內容。例如 C 語言中使用定義的全域性變數,
且定義時賦予“非 0 值”給該變數進行初始化。
q ZI-data: Zero Initialie data,即 0 初始化資料,它指初始化為“0 值”的可讀寫數
據域,它與 RW-data 的區別是程式剛執行時這些資料初始值全都為 0,而後續運
行過程與 RW-data 的性質一樣,它們也常駐在 RAM 區,因而應用程式可以更改
其內容。例如 C 語言中使用定義的全域性變數,且定義時賦予“0 值”給該變數進
行初始化(若定義該變數時沒有賦予初始值,編譯器會把它當 ZI-data 來對待,初
始化為 0);
q ZI-data 的棧空間(Stack)及堆空間(Heap):在 C 語言中,函式內部定義的區域性變數
屬於棧空間,進入函式的時候從向棧空間申請記憶體給區域性變數,退出時釋放區域性
變數,歸還記憶體空間。而使用 malloc 動態分配的變數屬於堆空間。在程式中的棧
空間和堆空間都是屬於 ZI-data 區域的,這些空間都會被初始值化為 0 值。編譯器
給出的 ZI-data 佔用的空間值中包含了堆疊的大小(經實際測試,若程式中完全沒
有使用 malloc 動態申請堆空間,編譯器會優化,不把堆空間計算在內)。

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

 

 

圖中的左側是應用程式的儲存狀態,右側是執行狀態,而上方是 RAM 儲存器區域,
下方是 ROM 儲存器區域。
程式在儲存狀態時, 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 系統。

 

 

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

設定環境變數
呼叫這些編譯工具,需要用到 Windows 的“命令列提示符工具”,為了讓命令列方便
地找到這些工具,我們先把工具鏈的目錄新增到系統的環境變數中。檢視本機工具鏈所在
的具體目錄可根據上一小節講解的工程編譯提示輸出資訊中找到,如本機的路徑為
“D:\work\keil5\ARM\ARMCC\bin”。
1. 新增路徑到 PATH 環境變數
本文以 Win7 系統為例新增工具鏈的路徑到 PATH 環境變數,其它系統是類似的。
(1) 右鍵電腦系統的“計算機圖示”,在彈出的選單中選擇“屬性”,見圖 48-6;

 

 

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

 

 

(3) 開啟 Windows 的命令列,點選系統的“開始選單”,在搜尋框輸入
“cmd”,在搜尋結果中點選“cmd.exe”即可開啟命令列,見圖 48-8;

 

(4) 在彈出的命令列視窗中輸入“fromelf”回車,若視窗打印出 formelf 的幫助說明,
那麼路徑正常,就可以開始後面的工作了;若提示“不是內部名外部命令,也不
是可執行的程式…”資訊, 說明路徑不對,請重新配置環境變數,並確認該工作
目錄下有編譯工具鏈。
這個過程本質就是讓命令列通過“PATH”路徑找到“fromelf.exe”程式執行,預設運
行“fromelf.exe”時它會輸出自己的幫助資訊,這就是工具鏈的呼叫過程, MDK 本質上也
是如此呼叫工具鏈的,只是它整合為 GUI,相對於命令列對使用者更友好,畢竟上述配置環
境變數的過程已經讓新手煩躁了。

armcc、 armasm 及 armlink
略,在linux arm開發中已經熟悉過了。

補充MDK生成bin檔案:

只能通過命令列:

 

命令列:#K\ARM\ARMCC\bin\fromelf.exe --bin -o #L.bin #L  (#L.bin可以改成@L.bin,主要是生成的路徑不同,@生成在工程檔案資料夾,#生成在輸出檔案資料夾裡)

這樣就可以生成bin檔案了。

MDK 工程的檔案型別
除了上述編譯過程生成的檔案, MDK 工程中還包含了各種各樣的檔案,下面我們統一
介紹, MDK 工程的常見檔案型別見表 48-3。

 

 

uvprojx、 uvoptx、 uvguix 及 ini 工程檔案
在工程的“Project”目錄下主要是 MDK 工程相關的檔案,見圖 48-17。

 

 

  1. uvprojx 檔案
    uvprojx 檔案就是我們平時雙擊開啟的工程檔案,它記錄了整個工程的結構,如晶片類
    型、工程包含了哪些原始檔等內容,見圖 48-18。

 

 

  1. uvoptx 檔案
    uvoptx 檔案記錄了工程的配置選項,如下載器的型別、變數跟蹤配置、斷點位置以及
    當前已開啟的檔案等等,見圖 48-19。

 

 

  1. uvprojx 檔案
    uvguix 檔案記錄了 MDK 軟體的 GUI 佈局,如程式碼編輯區視窗的大小、編譯輸出提示
    視窗的位置等等。

 

 

uvprojx、 uvoptx 及 uvguix 都是使用 XML 格式記錄的檔案,若使用記事本開啟可以看
到 XML 程式碼,見圖 48-17。而當使用 MDK 軟體開啟時,它根據這些檔案的 XML 記錄加
載工程的各種引數,使得我們每次重新開啟工程時,都能恢復上一次的工作環境。

 

 

這些工程引數都是當 MDK 正常退出時才會被寫入儲存,所以若 MDK 錯誤退出時(如
使用 Windows 的工作管理員強制關閉),工程配置引數的最新更改是不會被記錄的,重新
開啟工程時要再次配置。根據這幾個檔案的記錄型別,可以知道 uvprojx 檔案是最重要的,
刪掉它我們就無法再正常開啟工程了,而 uvoptx 及 uvguix 檔案並不是必須的,可以刪除,重新使用 MDK 開啟 uvprojx 工程檔案後,會以預設引數重新建立 uvoptx 及 uvguix 檔案。
(所以當使用 Git/SVN 等程式碼管理的時候,往往只保留 uvprojx 檔案)

Output 目錄下生成的檔案
點選 MDK 中的編譯按鈕,它會根據工程的配置及工程中的原始檔輸出各種物件和列
表文件,在工程的“Options for Targe->Output->Select Folder for Objects”和“Options for
Targe->Listing->Select Folder for Listings”選項配置它們的輸出路徑,見圖 48-22 和圖 48-23。

 

 

  1. lib 庫檔案
    在某些場合下我們需要提供給第三方一個可用的程式碼庫,但不希望對方看到原始碼,這個時候我們就可以把工程生成 lib 檔案(Library file)提供給對方,在 MDK 中可配置“Options for Target->Create Library”選項把工程編譯成庫檔案,見圖 48-25。

 

 

工程中生成可執行檔案或庫檔案只能二選一,預設編譯是生成可執行檔案的,可執行
檔案即我們下載到晶片上直接執行的機器碼。
得到生成的*.lib 檔案後,可把它像 C 檔案一樣新增到其它工程中,並在該工程呼叫 lib提供的函式介面,除了不能看到*.lib 檔案的原始碼,在應用方面它跟 C 原始檔沒有區別。

  1. dep、 d 依賴檔案
    *.dep 和*.d 檔案(Dependency file)記錄的是工程或其它檔案的依賴, 主要記錄了引用的標頭檔案路徑, 其中*.dep 是整個工程的依賴, 它以工程名命名, 而*.d 是單個原始檔的依賴,它們以對應的原始檔名命名。這些記錄使用文字格式儲存,我們可直接使用記事本開啟,見圖 48-26 和圖 48-27。

 

 

 

  1. crf 交叉引用檔案
    *.crf 是交叉引用檔案(Cross-Reference file),它主要包含了瀏覽資訊(browse information),即原始碼中的巨集定義、變數及函式的定義和宣告的位置。我們在程式碼編輯器中點選“Go To Definition Of ‘xxxx’”可實現瀏覽跳轉,見圖 48-28,跳轉的時候, MDK 就是通過*.crf 檔案查找出跳轉位置的。

 

 

通過配置 MDK 中的“Option for Target->Output->Browse Information”選項可以設定編譯時是否生成瀏覽資訊,見圖 48-29。只有勾選該選項並編譯後,才能實現上面的瀏覽跳轉功能。

 

  1. o、 axf 及 elf 檔案
    *.o、 *.elf、 *.axf、 *.bin 及*.hex 檔案都儲存了編譯器根據原始碼生成的機器碼,根據應用場合的不同,它們又有所區別。

ELF 檔案說明
*.o、 *.elf、 *.axf 以及前面提到的 lib 檔案都是屬於目標檔案,它們都是使用 ELF 格式來儲存的,關於 ELF 格式的詳細內容請參考配套資料裡的《ELF 檔案格式》文件瞭解,它講解的是 Linux 下的 ELF 格式,與 MDK 使用的格式有小區別,但大致相同。在本教程中,僅講解 ELF 檔案的核心概念。
ELF 是 Executable and Linking Format 的縮寫,譯為可執行連結格式,該格式用於記錄目標檔案的內容。在 Linux 及 Windows 系統下都有使用該格式的檔案(或類似格式)用於記錄應用程式的內容,告訴作業系統如何連結、載入及執行該應用程式。

目標檔案主要有如下三種類型:
(1) 可重定位的檔案(Relocatable File), 包含基礎程式碼和資料,但它的程式碼及資料都沒有指定絕對地址,因此它適合於與其他目標檔案連結來建立可執行檔案或者共享
目標檔案。 這種檔案一般由編譯器根據原始碼生成。
例如 MDK 的 armcc 和 armasm 生成的*.o 檔案就是這一類,另外還有 Linux
的*.o 檔案, Windows 的 *.obj 檔案。
(2) 可執行檔案(Executable File) ,它包含適合於執行的程式, 它內部組織的程式碼資料都有固定的地址(或相對於基地址的偏移),系統可根據這些地址資訊把程式載入到記憶體執行。 這種檔案一般由連結器根據可重定位檔案連結而成,它主要是組織各個可重定位檔案,給它們的程式碼及資料一一打上地址標號,固定其在程式內部
的位置,連結後,程式內部各種程式碼及資料段不可再重定位(即不能再參與連結器
的連結)。
例如 MDK 的 armlink 生成的*.elf 及*.axf 檔案, (使用 gcc 編譯工具可生成
*.elf 檔案,用 armlink 生成的是*.axf 檔案, *.axf 檔案在*.elf 之外,增加了除錯使用的資訊,其餘區別不大,後面我們僅講解*.axf 檔案),另外還有 Linux 的/bin/bash 檔案, Windows 的*.exe 檔案。
(3) 共享目標檔案(Shared Object File), 它的定義比較難理解,我們直接舉例, MDK
生成的*.lib 檔案就屬於共享目標檔案,它可以繼續參與連結,加入到可執行檔案
之中。 另外, Linux 的.so,如/lib/ glibc-2.5.so, Windows 的 DLL 都屬於這一類。

  • o 檔案與 axf 檔案的關係
    根據上面的分類,我們瞭解到, *.axf 檔案是由多個*.o 檔案連結而成的,而*.o 檔案由相應的原始檔編譯而成,一個原始檔對應一個*.o 檔案。它們的關係見圖 48-31。

 

 

圖中的中間代表的是 armlink 連結器,在它的右側是輸入連結器的*.o 檔案,左側是它輸出的*axf 檔案。
可以看到,由於都使用 ELF 檔案格式, *.o 與*.axf 檔案的結構是類似的,它們包含ELF 檔案頭、程式頭、節區(section)以及節區頭部表。各個部分的功能說明如下:
q ELF 檔案頭用來描述整個檔案的組織,例如資料的大小端格式,程式頭、節區頭
在檔案中的位置等。
q 程式頭告訴系統如何載入程式,例如程式主體儲存在本檔案的哪個位置,程式的大小,程式要載入到記憶體什麼地址等等。 MDK 的可重定位檔案*.o 不包含這部分內容,因為它還不是可執行檔案,而 armlink 輸出的*.axf 檔案就包含該內容了。
q 節區是*.o 檔案的獨立資料區域,它包含提供給連結檢視使用的大量資訊,如指令(Code)、資料(RO、 RW、 ZI-data)、符號表(函式、變數名等)、重定位資訊等,例如每個由 C 語言定義的函式在*.o 檔案中都會有一個獨立的節區;
q 儲存在最後的節區頭則包含了本檔案節區的資訊,如節區名稱、大小等等。
總的來說,連結器把各個*.o 檔案的節區歸類、排列,根據目標器件的情況編排地址生成輸出,彙總到*.axf 檔案。例如,見圖 48-32,“多彩流水燈”工程中在“bsp_led.c”檔案中有一個 LED_GPIO_Config 函式,而它內部呼叫了“stm32f4xx_gpio.c”的 GPIO_Init 函式,經過 armcc 編譯後, LED_GPIO_Config 及 GPIO_Iint 函式都成了指令程式碼,分別儲存在 bsp_led.o 及 stm32f4xx_gpio.o 檔案中,這些指令在*.o 檔案都沒有指定地址,僅包含了內容、大小以及呼叫的連結資訊,而經過連結器後,連結器給它們都分配了特定的地址,並且把地址根據呼叫指向連結起來。

 

ELF 檔案頭
接下來我們看看具體檔案的內容,使用 fromelf 檔案可以檢視*.o、 *.axf 及*.lib 檔案的
ELF 資訊。
使用命令列,切換到檔案所在的目錄,輸入“fromelf –text –v bsp_led.o”命令,可控
制輸出 bsp_led.o 的詳細資訊,見圖 48-33。 利用“-c、 -z”等選項還可輸出反彙編指令文
件、程式碼及資料檔案等資訊,請親手嘗試一下。

 

生成 bin 檔案
使用 MDK 生成 bin 檔案需要使用 fromelf 命令,在 MDK 的“Options For Target->Users”中加入圖 48-35 中的命令。

 

 

圖中的指令內容為:
“fromelf --bin --output ..\..\Output\多彩流水燈.bin ..\..\Output\多彩流水燈.axf”
該指令是根據本機及工程的配置而寫的,在不同的系統環境或不同的工程中,指令內容都不一樣,我們需要理解它,才能為自己的工程定製指令,首先看看 fromelf 的幫助,見圖 48-36。

 

 

我們在 MDK 輸入的指令格式是遵守 fromelf 幫助裡的指令格式說明的,其格式為:
“fromelf [options] input_file”
其中 optinos 是指令選項,一個指令支援輸入多個選項,每個選項之間使用空格隔開,我們的例項中使用“--bin”選項設定輸出 bin 檔案,使用“--output file”選項設定輸出檔案的名字為“..\..\Output\多彩流水燈.bin”,這個名字是一個相對路徑格式,如果不瞭解如何使用“..\”表示路徑,可使用 MDK 命令輸入框後面的資料夾圖示開啟檔案瀏覽器選擇檔案,在命令的最後使用“..\..\Output\多彩流水燈.axf”作為命令的輸入檔案。具體的格式分解見圖 48-37。

 

 

fromelf 需要根據工程的*.axf 檔案輸入來轉換得到 bin 檔案,所以在命令的輸入檔案引數中要選擇本工程對應的*.axf 檔案,在 MDK 命令輸入欄中,我們把 fromelf 指令放置在“After Build/Rebuild” (工程構建完成後執行)一欄也是基於這個考慮,這樣設定後,工程構建完成生成了最新的*.axf 檔案, MDK 再執行 fromelf 指令,從而得到最新的 bin 檔案。設定完成生成 hex 的選項或添加了生成 bin 的使用者指令後,點選工程的編譯(build)按鈕,重新編譯工程,成功後可看到圖 48-38 中的輸出。開啟相應的目錄即可找到檔案,若找不到 bin 檔案,請檢視提示輸出欄執行指令的資訊,根據資訊改正 fromelf 指令。

 

 

hex 檔案格式
hex 是 Intel 公司制定的一種使用 ASCII 文字記錄機器碼或常量資料的檔案格式,這種檔案常常用來記錄將要儲存到 ROM 中的資料,絕大多數下載器支援該格式。
一個 hex 檔案由多條記錄組成,而每條記錄由五個部分組成,格式形如
“:llaaaatt[dd…]cc”,例如本“多彩流水燈”工程生成的 hex 檔案前幾條記錄見程式碼清單48-9。

 

 

 

例如, 程式碼清單 48-9 中的第一條記錄解釋如下:
(1) 02:表示這條記錄資料區的長度為 2 位元組;
(2) 0000:表示這條記錄要儲存到的地址;
(3) 04:表示這是一條擴充套件線性地址記錄;

(4) 0800:由於這是一條擴充套件線性地址記錄,所以這部分表示地址的高 16 位,與前面的“0000”結合在一起,表示要擴充套件的線性地址為“0x0800 0000”,這正好是
STM32 內部 FLASH 的首地址;
(5) F2:表示校驗和,它的值為(0x02+0x00+0x00+0x04+0x08+0x00)%256 的值再取補碼。
再來看第二條記錄:
(1) 10:表示這條記錄資料區的長度為 2 位元組;
(2) 0000:表示這條記錄所在的地址,與前面的擴充套件記錄結合,表示這條記錄要儲存的 FLASH 首地址為(0x0800 0000+0x0000);
(3) 00:表示這是一條資料記錄,資料區的是地址;
(4) 00040020C10100081B030008A3020008:這是要按地址儲存的資料;
(5) 2F:校驗和為了更清楚地對比 bin、 hex 及 axf 檔案的差異,我們來檢視這些檔案內部記錄的資訊來進行對比。

 

 

如果您想要親自閱讀自己電腦上的 bin 檔案,推薦使用sublime 軟體開啟,它可以把二進位制數以 ASCII 碼呈現出來,便於閱讀。

htm 靜態呼叫圖檔案
在 Output 目錄下,有一個以工程檔案命名的字尾為*.bulid_log.htm 及*.htm 檔案,如“多彩流水燈.bulid_log.htm”及“多彩流水燈.htm”,它們都可以使用瀏覽器開啟。其中*.build_log.htm 是工程的構建過程日誌,而*.htm 是連結器生成的靜態呼叫圖檔案。
在靜態呼叫圖檔案中包含了整個工程各種函式之間互相呼叫的關係圖,而且它還給出了靜態佔用最深的棧空間數量以及它對應的呼叫關係鏈。
例如圖 48-43 是“多彩流水燈.htm”檔案頂部的說明。

 

 

該檔案說明了本工程的靜態棧空間最大佔用 56 位元組(Maximum Stack Usage:56bytes),這個佔用最深的靜態呼叫為“main->LED_GPIO_Config->GPIO_Init”。注意這裡給出的空間只是靜態的棧使用統計,連結器無法統計動態使用情況,例如連結器無法知道遞迴函式的遞迴深度。在本檔案的後面還可查詢到其它函式的呼叫情況及其它細節。利用這些資訊,我們可以大致瞭解工程中應該分配多少空間給棧,有空間餘量的情況下,一般會設定比這個靜態最深棧使用量大一倍,在 STM32 中可修改啟動檔案改變堆疊的大小;如果空間不足,可從本檔案中瞭解到呼叫深度的資訊,然後優化該程式碼。
注意:
查看了各個工程的靜態呼叫圖檔案統計後,我們發現本書提供的一些比較大規模的工程例子,靜態棧呼叫最大深度都已超出 STM32 啟動檔案預設的棧空間大小 0x00000400,即 1024 位元組,但在當時的除錯過程中卻沒有發現錯誤,因此我們也沒有修改棧的預設大小(有一些工程除錯時已發現問題,它們的棧空間就已經被我們改大了),雖然這些工程實際執行並沒有錯誤,但這可能只是因為它使用的棧溢位 RAM 空間恰好沒被程式其它部分修改而已。所以,建議您在實際的大型工程應用中(特別是使用了各種外部庫時,如Lwip/emWin/Fatfs 等),要檢視本靜態呼叫圖檔案,瞭解程式的棧使用情況,給程式分配合適的棧空間。

Listing 目錄下的檔案
在 Listing 目錄下包含了*.map 及*.lst 檔案,它們都是文字格式的,可使用 Windows 的記事本軟體開啟。其中 lst 檔案僅包含了一些彙編符號的連結資訊,我們重點分析 map 檔案。

  1. map 檔案說明
    map 檔案是由連結器生成的,它主要包含交叉連結資訊,檢視該檔案可以瞭解工程中各種符號之間的引用以及整個工程的 Code、 RO-data、 RW-data 以及 ZI-data 的詳細及彙總資訊。它的內容中主要包含了“節區的跨檔案引用”、“刪除無用節區”、“符號映像表”、“儲存器映像索引”以及“映像元件大小”,各部分介紹如下:

 

在這部分中,詳細列出了各個*.o 檔案之間的符號引用。由於*.o 檔案是由 asm 或 c/c++原始檔編譯後生成的,各個檔案及檔案內的節區間互相獨立,連結器根據它們之間的互相引用連結起來,連結的詳細資訊在這個“Section Cross References”一一列出。例如,開頭部分說明的是 startup_stm32f429_439xx.o 檔案中的“RESET”節區分為它使用的“__initial_sp” 符號引用了同文件“STACK”節區。也許我們對啟動檔案不熟悉,不清楚這究竟是什麼,那我們繼續瀏覽,可看到 main.o檔案的引用說明,如說明 main.o 檔案的 i.main 節區為它使用的 LED_GPIO_Config 符號引用了 bsp_led.o 檔案的 i.LED_GPIO_Config 節區。同樣地,下面還有 bsp_led.o 檔案的引用說明,如說明了 bsp_led.o 檔案的i.LED_GPIO_Config 節區為它使用的 GPIO_Init 符號引用了 stm32f4xx_gpio.o 檔案的i.GPIO_Init 節區。
可以瞭解到,這些跨檔案引用的符號其實就是原始檔中的函式名、變數名。有時在構
建工程的時候,編譯器會輸出 “Undefined symbol xxx (referred from xxx.o)” 這樣的提示,該提示的原因就是在連結過程中,某個檔案無法在外部找到它引用的標號,因而產生連結錯誤。例如,見圖 48-44,我們把 bsp_led.c 檔案中定義的函式 LED_GPIO_Config 改名為LED_GPIO_ConfigABCD,而不修改 main.c 檔案中的呼叫,就會出現 main 檔案無法找到LED_GPIO_Config 符號的提示。

刪除無用節區
map 檔案的第二部分是刪除無用節區的說明(Removing Unused input sections from theimage.),見程式碼清單 48-11。

 

 

這部分列出了在連結過程它發現工程中未被引用的節區,這些未被引用的節區將會被
刪除(指不加入到*.axf 檔案,不是指在*.o 檔案刪除),這樣可以防止這些無用資料佔用程式空間。

例如,上面的資訊中說明 startup_stm32f429_439xx.o 中的 HEAP(在啟動檔案中定義的用於動態分配的“堆”區)以及 stm32f4xx_adc.o 的各個節區都被刪除了,因為在我們這個工程中沒有使用動態記憶體分配,也沒有引用任何 stm32f4xx_adc.c 中的內容。由此也可以知道,雖然我們把 STM32 標準庫的各個外設對應的 c 庫檔案都新增到了工程,但不必擔心這會使工程變得臃腫,因為未被引用的節區內容不會被加入到最終的機器碼檔案中。

符號映像表
map 檔案的第三部分是符號映像表(Image Symbol Table), 見程式碼清單 48-12。

 

 

這個表列出了被引用的各個符號在儲存器中的具體地址、佔據的空間大小等資訊。如
我們可以查到 LED_GPIO_Config 符號儲存在 0x080002a5 地址,它屬於 Thumb Code 型別,大小為 106 位元組,它所在的節區為 bsp_led.o 檔案的 i.LED_GPIO_Config 節區。
儲存器映像索引
map 檔案的第四部分是儲存器映像索引(Memory Map of the image), 見程式碼清單 48-13。

 

本工程的儲存器映像索引分為 ER_IROM1 及 RW_IRAM1 部分,它們分別對應 STM32
內部 FLASH 及 SRAM 的空間。相對於符號映像表,這個索引表描述的單位是節區,而且
它描述的主要資訊中包含了節區的型別及屬性,由此可以區分 Code、 RO-data、 RW-data
及 ZI-data。
例如,從上面的表中我們可以看到 i.LED_GPIO_Config 節區儲存在內部 FLASH 的
0x080002a4 地址,大小為 0x00000074,型別為 Code,屬性為 RO。而程式的 STACK 節區(棧空間)儲存在 SRAM 的 0x20000000 地址,大小為 0x00000400,型別為 Zero,屬性為RW(即 RW-data) 。

映像元件大小

map 檔案的最後一部分是包含映像元件大小的資訊(Image component sizes),這也是最常
查詢的內容,見程式碼清單 48-14。

 

這部分包含了各個使用到的*.o 檔案的空間彙總資訊、整個工程的空間彙總資訊以及佔
用不同型別儲存器的空間彙總資訊,它們分類描述了具體佔據的 Code、 RO-data、 RW-data及 ZI-data 的大小,並根據這些大小統計出佔據的 ROM 總空間。
我們僅分析最後兩部分資訊,如 Grand Totals 一項,它表示整個程式碼佔據的所有空間
資訊,其中 Code 型別的資料大小為 1012 位元組,這部分包含了 84 位元組的指令資料(inc .data)已算在內,另外 RO-data 佔 444 位元組, RW-data 佔 0 位元組, ZI-data 佔 1024 位元組。在它的下面兩行有一項 ROM Totals 資訊,它列出了各個段所佔據的 ROM 空間,除了 ZI-data 不佔ROM 空間外,其餘項都與 Grand Totals 中相等(RW-data 也佔據 ROM 空間,只是本工程中沒有 RW-data 型別的資料而已)。
最後一部分列出了只讀資料(RO)、可讀寫資料(RW)及佔據的 ROM 大小。其中只讀數
據大小為 1456 位元組, 它包含 Code 段及 RO-data 段; 可讀寫資料大小為 1024 位元組,它包含RW-data 及 ZI-data 段;佔據的 ROM 大小為 1456 位元組,它除了 Code 段和 RO-data 段,還包含了執行時需要從 ROM 載入到 RAM 的 RW-data 資料。
綜合整個 map 檔案的資訊,可以分析出,當程式下載到 STM32 的內部 FLASH 時,需
要使用的內部 FLASH 是從 0x0800 0000 地址開始的大小為 1456 位元組的空間;當程式執行時,需要使用的內部 SRAM 是從 0x20000000 地址開始的大小為 1024 位元組的空間。
粗略一看, 發現這個小程式竟然需要 1024 位元組的 SRAM,實在說不過去,但仔細分析
map 檔案後,可瞭解到這 1024 位元組都是 STACK 節區的空間(即棧空間),棧空間大小是在
啟動檔案中定義的,這 1024 位元組是預設值(0x00000400)。它是提供給 C 語言程式區域性變數申請使用的空間,若我們確認自己的應用程式不需要這麼大的棧,完全可以修改啟動檔案,
把它改小一點,檢視前面講解的 htm 靜態呼叫圖檔案可瞭解靜態的棧呼叫情況,可以用它
作為參考。

sct 分散載入檔案的格式與應用
1. sct 分散載入檔案簡介

當工程按預設配置構建時, MDK 會根據我們選擇的晶片型號,獲知晶片的內部
FLASH 及內部 SRAM 儲存器概況,生成一個以工程名命名的字尾為*.sct 的分散載入檔案
(Linker Control File, scatter loading),連結器根據該檔案的配置分配各個節區地址,生成分
散載入程式碼,因此我們通過修改該檔案可以定製具體節區的儲存位置。
例如可以設定原始檔中定義的所有變數自動按地址分配到外部 SDRAM,這樣就不需
要再使用關鍵字“__attribute__”按具體地址來指定了;利用它還可以控制程式碼的載入區與
執行區的位置,例如可以把程式程式碼儲存到單位容量價格便宜的 NAND-FLASH 中,但在
NAND-FLASH 中的程式碼是不能像內部 FLASH 的程式碼那樣直接提供給核心執行的,這時可
通過修改分散載入檔案,把程式碼載入區設定為 NAND-FLASH 的程式位置,而程式的執行
區設定為 SDRAM 中的位置,這樣連結器就會生成一個配套的分散載入程式碼,該程式碼會把
NAND-FLASH 中的程式碼載入到 SDRAM 中,核心再從 SDRAM 中執行主體程式碼,大部分
執行 Linux 系統的程式碼都是這樣載入的。

分散載入檔案的格式
下面先來看看 MDK 預設使用的 sct 檔案,在 Output 目錄下可找到“多彩流水燈.sct”,
該檔案記錄的內容見程式碼清單 48-15。

 

 

在預設的 sct 檔案配置中僅分配了 Code、 RO-data、 RW-data 及 ZI-data 這些大區域的地址,連結時各個節區(函式、變數等)直接根據屬性排列到具體的地址空間。
sct 檔案中主要包含描述載入域及執行域的部分,一個檔案中可包含有多個載入域,而
一個載入域可由多個部分的執行域組成。同等級的域之間使用花括號“{}”分隔開,最外
層的是載入域,第二層“{}”內的是執行域,其整體結構見圖 48-45。

 

 

載入域
sct 檔案的載入域格式見程式碼清單 48-16。

 

 

q 載入域名:名稱,在 map 檔案中的描述會使用該名稱來標識空間。如本例中只有
一個載入域,該域名為 LR_IROM1。
q 基地址+地址偏移:這部分說明了本載入域的基地址,可以使用+號連線一個地址
偏移,算進基地址中,整個載入域以它們的結果為基地址。如本例中的載入域基
地址為 0x08000000,剛好是 STM32 內部 FLASH 的基地址。
q 屬性列表:屬性列表說明了載入域的是否為絕對地址、 N 位元組對齊等屬性,該配
置是可選的。本例中沒有描述載入域的屬性。
q 最大容量:最大容量說明了這個載入域可使用的最大空間,該配置也是可選的,
如果加上這個配置後,當連結器發現工程要分配到該區域的空間比容量還大,它
會在工程構建過程給出提示。本例中的載入域最大容量為 0x00100000,即 1MB,正是本型號 STM32 內部 FLASH 的空間大小。

 

 

輸入節區描述
配合載入域及執行域的配置,在相應的域配置“輸入節區描述”即可控制該節區儲存
到域中,其格式見程式碼清單 48-18。

 

 

q 模組選擇樣式:模組選擇樣式可用於選擇 o 及 lib 目標檔案作為輸入節區,它可以
直接使用目標檔名或“*”萬用字元,也可以使用“.ANY”。例如,使用語句“bsp_led.o”可以選擇 bsp_led.o 檔案,使用語句“*.o”可以選擇所有 o 檔案,使用“*.lib”可以選擇所有 lib 檔案,使用“*”或“.ANY”可以選擇所有的 o 檔案及 lib 檔案。其中“.ANY”選擇語句的優先順序是最低的,所有其它選擇語句選擇完剩下的資料才會被“.ANY”語句選中。

q 輸入節區樣式:我們知道在目標檔案中會包含多個節區或符號,通過輸入節區樣
式可以選擇要控制的節區。
示例檔案中“(RESET, +First)”語句的 RESET 就是輸入節區樣式,它選擇
了名為 RESET 的節區,並使用後面介紹的節區特性控制字“+First”表示它要存
儲到本區域的第一個地址。示例檔案中的“*(InRoot$$Sections)”是一個連結器支
持的特殊選擇符號,它可以選擇所有標準庫裡要求儲存到 root 區域的節區,如
__main.o、 __scatter*.o 等內容。

q 輸入符號樣式:同樣地,使用輸入符號樣式可以選擇要控制的符號,符號樣式需
要使用“:gdef:”來修飾。例如可以使用“*(:gdef:Value_Test)”來控制選擇符號“Value_Test”。
q 輸入節區屬性:通過在模組選擇樣式後面加入輸入節區屬性,可以選擇樣式中不
同的內容,每個節區屬性描述符前要寫一個“+”號,使用空格或“,”號分隔開,可以使用的節區屬性描述符見表 48-6。

  

例如,示例檔案中使用“.ANY(+RO)”選擇剩餘所有節區 RO 屬性的內容都分配
到執行域 ER_IROM1 中,使用“.ANY(+RW +ZI)”選擇剩餘所有節區 RW 及 ZI 屬性
的內容都分配到執行域 RW_IRAM1 中。
q 節區特性:節區特性可以使用“+FIRST”或“+LAST”選項配置它要儲存到的位置,
FIRST 儲存到區域的頭部, LAST 儲存到尾部。通常重要的節區會放在頭部,而
CheckSum(校驗和)之類的資料會放在尾部。
例如示例檔案中使用“(RESET,+First)”選擇了 RESET 節區,並要求把它放置到
本區域第一個位置,而 RESET 是工程啟動程式碼中定義的向量表,見程式碼清單 48-19,
該向量表中定義的堆疊頂和復位向量指標必須要儲存在內部 FLASH 的前兩個地址,
這樣 STM32 才能正常啟動,所以必須使用 FIRST 控制它們儲存到首地址。

 

 

 

總的來說,我們的 sct 示例檔案配置如下:程式的載入域為內部 FLASH 的 0x08000000,
最大空間為 0x00100000;程式的執行基地址與載入基地址相同,其中 RESET 節區定義的
向量表要儲存在內部 FLASH 的首地址,且所有 o 檔案及 lib 檔案的 RO 屬性內容都儲存在內部 FLASH 中;程式執行時 RW 及 ZI 區域都儲存在以 0x20000000 為基地址,大小為
0x00030000 的空間(192KB),這部分正好是 STM32 內部主 SRAM 的大小。
連結器根據 sct 檔案連結,連結後各個節區、符號的具體地址資訊可以在 map 檔案
中檢視。

通過 MDK 配置選項來修改 sct 檔案
瞭解 sct 檔案的格式後,可以手動編輯該檔案控制整個工程的分散載入配置,但 sct 文
件格式比較複雜,所以 MDK 提供了相應的配置選項可以方便地修改該檔案,這些選項配
置能滿足基本的使用需求,本小節將對這些選項進行說明。
選擇 sct 檔案的產生方式
首先需要選擇 sct 檔案產生的方式,選擇使用 MDK 生成還是使用使用者自定義的 sct 文
件。在 MDK 的“Options for Target->Linker->Use Memory Layout from Target Dialog”選項
即可配置該選擇,見圖 48-46。

 

 

該選項的譯文為“是否使用 Target 對話方塊中的儲存器分佈配置”,勾選後,它會根據
“Options for Target”對話方塊中的選項生成 sct 檔案,這種情況下,即使我們手動開啟它生
成的 sct 檔案編輯也是無效的,因為每次構建工程的時候, MDK 都會生成新的 sct 檔案覆蓋舊檔案。該選項在 MDK 中是預設勾選的,若希望 MDK 使用我們手動編輯的 sct 檔案構建工程,需要取消勾選,並通過 Scatter File 框中指定 sct 檔案的路徑,見圖 48-47。

 

 

通過 Target 對話方塊控制儲存器分配
若我們在 Linker 中勾選了“使用 Target 對話方塊的儲存器佈局”選項,那麼“Options
for Target”對話方塊中的儲存器配置就生效了。主要配置是在 Device 標籤頁中選擇晶片的類
型,設定晶片基本的內部儲存器資訊以及在 Target 標籤頁中細化具體的儲存器配置(包括外部儲存器),見圖 48-48 及圖 48-49。

 

 

圖中 Device 標籤頁中選定了晶片的型號為 STM32F429IGTx,選中後,在 Target 標籤
頁中的儲存器資訊會根據晶片更新。

 

 

在 Target 標籤頁中儲存器資訊分成只讀儲存器(Read/Only Memory Areas)和可讀寫儲存
器(Read/Write Memory Areas)兩類,即 ROM 和 RAM,而且它們又細分成了片外儲存器
(off-chip)和片記憶體儲器(on-chip)兩類。
例如,由於我們已經選定了晶片的型號, MDK 會自動根據晶片型號填充片內的 ROM
及 RAM 資訊,其中的 IROM1 起始地址為 0x80000000,大小為 0x100000,正是該 STM32
型號的內部 FLASH 地址及大小;而 IRAM1 起始地址為 0x20000000,大小為 0x30000,正是該 STM32 內部主 SRAM 的地址及大小。圖中的 IROM1 及 IRAM1 前面都打上了勾,表示這個配置資訊會被採用,若取消勾選,則該儲存配置資訊是不會被使用的。
在標籤頁中的 IRAM2 一欄預設也填寫了配置資訊,它的地址為 0x10000000,大小為0x10000,這是 STM32F4 系列特有的內部 64KB 高速 SRAM(被稱為 CCM)。當我們希望使
用這部分儲存空間的時候需要勾選該配置,另外要注意這部分高速 SRAM 僅支援 CPU 總
線的訪問,不能通過外設訪問。
下面我們嘗試修改 Target 標籤頁中的這些儲存資訊,例如,按照圖 48-50 中的 1 配置,
把 IRAM1 的基地址改為 0x20001000,然後編譯工程,檢視到工程的 sct 檔案如程式碼清單
48-20 所示;當按照圖 48-50 中的 2 配置時,同時使用 IRAM1 和 IRAM2,然後編譯工程,可檢視到工程的 sct 檔案如程式碼清單 48-21 所示。

 

 

可以發現, sct 檔案都根據 Target 標籤頁做出了相應的改變,除了這種修改外,在
Target 標籤頁上還控制同時使用 IRAM1 和 IRAM2、加入外部 RAM(如外接的 SDRAM),
外部 FLASH 等。
控制檔案分配到指定的儲存空間
設定好儲存器的資訊後,可以控制各個原始檔定製到哪個部分儲存器,在 MDK 的工
程檔案欄中,選中要配置的檔案,右鍵,並在彈出的選單中選擇“Options for File xxxx”
即可彈出一個檔案配置對話方塊,在該對話方塊中進行儲存器定製,見圖 48-51。

 

 

在彈出的對話方塊中有一個“Memory Assignment”區域(儲存器分配),在該區域中可以
針對檔案的各種屬性內容進行分配,如 Code/Const 內容(RO)、 Zero Initialized Data 內容(ZIdata)以及 Other Data 內容(RW-data),點選下拉選單可以找到在前面 Target 頁面配置的IROM1、 IRAM1、 IRAM2 等儲存器。例如圖中我們把這個 bsp_led.c 檔案的 Other Data 屬性的內容分配到了 IRAM2 儲存器(在 Target 標籤頁中我們勾選了 IRAM1 及 IRAM2),當在bsp_led.c 檔案定義了一些 RW-data 內容時(如初值非 0 的全域性變數),該變數將會被分配到IRAM2 空間,配置完成後點選 OK,然後編譯工程,檢視到的 sct 檔案內容見程式碼清單48-22。

 

 

可以看到在 sct 檔案中的 RW_IRAM2 執行域中增加了一個選擇 bsp_led.o 中 RW 內容
的語句。
類似地,我們還可以設定某些檔案的程式碼段被儲存到特定的 ROM 中,或者設定某些
檔案使用的 ZI-data 或 RW-data 儲存到外部 SDRAM 中(控制 ZI-data 到 SDRAM 時注意還需要修改啟動檔案設定堆疊對應的地址,原啟動檔案中的地址是指向內部 SRAM 的)。
雖然 MDK 的這些儲存器配置選項很方便,但有很多高階的配置還是需要手動編寫 sct
檔案實現的,例如 MDK 選項中的內部 ROM 選項最多隻可以填充兩個選項位置,若想把內部 ROM 分成多片地址管理就無法實現了;另外 MDK 配置可控的最小粒度為檔案,若想控制特定的節區也需要直接編輯 sct 檔案。
 

-------------——————內容若有錯誤,請您務必指出,感謝讓我提高並給予我建議的你---———————————---轉載請註明出處——————————---——------