第38章 MDK的編譯過程及文件類型全解

38.1編譯過程
38.1.1編譯過程簡介
首先簡單瞭解下MDK的編譯過程,它與其它編譯器的工作過程是類似的,該過程圖例如下:
在這裏插入圖片描述
【編譯】MDK軟件使用的編譯器是armcc和armasm,它們根據每個c/c++和彙編源文件編譯成對應的以“.o”爲後綴名的對象文件(Object Code,也稱目標文件),其內容主要是從源文件編譯得到的機器碼,包含了代碼、數據以及調試使用的信息;
【 鏈接】鏈接器armlink把各個.o文件及庫文件鏈接成一個映像文件“.axf”或“.elf”;
【格式轉換】一般來說Windows或Linux系統使用鏈接器直接生成可執行映像文件elf後,內核根據該文件的信息加載後,就可以運行程序了.但在單片機平臺上,需要把該文件的內容加載到芯片上,所以還需要對鏈接器生成的elf映像文件利用格式轉換器fromelf轉換成“.bin”或“.hex”文件,交給下載器下載到芯片的FLASH中。
38.1.2具體工程中的編譯過程
在這裏插入圖片描述
構建工程的提示輸出主要分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文件。
38.2程序的組成、存儲、運行
38.2.1CODE、RO、RW、ZI Data域及堆棧空間
在工程的編譯提示輸出信息中有一個語句“Program Size:Code=xx RO-data=xx RW-data=xx ZI-data=xx”,它說明了程序各個域的大小,編譯後,應用程序中所有具有同一性質的數據(包括代碼)被歸到一個域,程序在存儲或運行的時候,不同的域會呈現不同的狀態,這些域的意義如下:
【Code】即代碼域,它指的是編譯器生成的機器指令,這些內容被存儲到Flash區。
【RO-data】Read Only data,即只讀數據域,它指程序中用到的只讀數據,這些數據被存儲在Flash區,因而程序不能修改其內容。例如C語言中const關鍵字定義的變量就是典型的RO-data。
【RW-data】Read Write data,即可讀寫數據域,它指初始化爲“非0值”的可讀寫數據,程序剛運行時,這些數據具有非0的初始值,且運行的時候它們會常駐在SRAM區,因而應用程序可以修改其內容。例如C語言中使用定義的全局變量,且定義時賦予“非0值”給該變量進行初始化
【ZI-data】Zero Initialie data,即0初始化數據,它指初始化爲“0值”的可讀寫數據域,它與RW-data的區別是程序剛運行時這些數據初始值全都爲0,而後續運行過程與RW-data的性質一樣,它們也常駐在SRAM區,因而應用程序可以更改其內容。例如C語言中使用定義的全局變量,且定義時賦予“0值”給該變量進行初始化(若定義該變量時沒有賦予初始值,編譯器會把它當ZI-data來對待,初始化爲0);
ZI-data的棧空間(Stack)及堆空間(Heap):在C語言中,函數內部定義的局部變量屬於棧空間,進入函數的時候從向棧空間申請內存給局部變量,退出時釋放局部變量,歸還內存空間。而使用malloc動態分配的變量屬於堆空間。在程序中的棧空間和堆空間都是屬於ZI-data區域的,這些空間都會被初始值化爲0值。編譯器給出的ZI-data佔用的空間值中包含了堆棧的大小(經實際測試,若程序中完全沒有使用malloc動態申請堆空間,編譯器會優化,不把堆空間計算在內)。
綜上所述,以程序的組成構件爲例,它們所屬的區域類別如下表:
在這裏插入圖片描述
38.2.2程序的存儲與運行

RW-data和ZI-data它們僅僅是初始值不一樣而已,爲什麼編譯器非要把它們區分開?這就涉及到程序的存儲狀態了,應用程序具有靜止狀態和運行狀態。
靜止態的程序被存儲在非易失存儲器中,如STM32的內部FLASH,因而系統掉電後也能正常保存。
但是當程序在運行狀態的時候,程序常常需要修改一些暫存數據,由於運行速度的要求,這些數據往往存放在內存中(SRAM),掉電後這些數據會丟失。
程序在靜止與運行的時候它在存儲器中的表現是不一樣的,如下圖:
在這裏插入圖片描述
圖中的左側是應用程序的存儲狀態,右側是運行狀態,而上方是RAM存儲器區域,下方是ROM存儲器區域(Flash)。
程序在存儲狀態時,RO節(RO section)及RW節都被保存在Flash區。當程序開始運行時,內核直接從Flash中讀取代碼,並且在執行主體代碼前,會先執行一段加載代碼,它把RW節數據從Flash複製到SRAM, 並且在SRAM加入ZI節,ZI節的數據都被初始化爲0。加載完後SRAM區準備完畢,正式開始執行主體程序。
編譯生成的RW-data的數據屬於圖中的RW節,ZI-data的數據屬於圖中的ZI節。是否需要掉電保存,這就是把RW-data與ZI-data區別開來的原因,因爲在SRAM創建數據的時候,默認值爲0,但如果有的數據要求初值非0,那就需要使用Flash記錄該初始值,運行時再複製到SRAM。
STM32的RO區域不需要加載到SRAM,內核直接從FLASH讀取指令運行。計算機系統的應用程序運行過程很類似,不過計算機系統的程序在存儲狀態時位於硬盤,執行的時候甚至會把上述的RO區域(代碼、只讀數據)加載到內存,加快運行速度,還有虛擬內存管理單元(MMU)輔助加載數據,使得可以運行比物理內存還大的應用程序。而STM32沒有MMU,所以無法支持Linux和Windows系統。
當程序存儲到STM32芯片的內部FLASH時,它佔用的空間是Code、RO-data及RW-data的總和,所以如果這些內容比STM32芯片的FLASH空間大,程序就無法被正常保存了。當程序在執行的時候,需要佔用內部SRAM空間,佔用的空間包括RW-data和ZI-data。
應用程序在各個狀態時各區域的組成如下表:
在這裏插入圖片描述
在MDK中,我們建立的工程一般會選擇芯片型號,選擇後就有確定的FLASH及SRAM大小,若代碼超出了芯片的存儲器的極限,編譯器會提示錯誤,這時就需要裁剪程序了,裁剪時可針對超出的區域來優化。
38.3編譯工具鏈
在前面編譯過程中,MDK調用了各種編譯工具,平時我們直接配置MDK,不需要學習如何使用它們,但瞭解它們是非常有好處的。
例如,若希望使用MDK編譯生成bin文件的,需要在MDK中輸入指令控制fromelf工具;在本章後面講解AXF及O文件的時候,需要利用fromelf工具查看其文件信息,這都是無法直接通過MDK做到的。
關於這些工具鏈的說明,在MDK的幫助手冊《ARM Development Tools》都有詳細講解,點擊MDK界面的“help->uVision Help”菜單可打開該文件。
38.3.1設置環境變量
調用這些編譯工具,需要用到Windows的“命令行提示符工具”,爲了讓命令行方便地找到這些工具,我們先把工具鏈的目錄添加到系統的環境變量中
查看本機工具鏈所在的具體目錄可根據上一小節講解的工程編譯提示輸出信息中找到,如本機的路徑爲E:\Keil_v5\ARM\ARMCC\bin。
1. 添加路徑到PATH環境變量
1)右鍵電腦系統的“計算機圖標”,在彈出的菜單中選擇“屬性”。
2)在彈出的屬性頁面依次點擊“高級系統設置”->“環境變量”,在用戶變量一欄中找到名爲“PATH”的變量,若沒有該變量,則新建一個。編輯“PATH”變量,在它的變量值中輸入工具鏈的路徑,如本機的是“E:\Keil_v5\ARM\ARMCC\bin”,注意要使用“分號;”讓它與其它路徑分隔開,輸入完畢後依次點確定。
在這裏插入圖片描述
3)打開Windows的命令行,點擊系統的“開始菜單”,在搜索框輸入“cmd”,在搜索結果中點擊“cmd.exe”即可打開命令行。
4)在彈出的命令行窗口中輸入“fromelf”回車,若窗口打印出formelf的幫助說明,那麼路徑正常,就可以開始後面的工作了;若提示“不是內部名外部命令,也不是可運行的程序…”信息,說明路徑不對,請重新配置環境變量,並確認該工作目錄下有編譯工具鏈。
這個添加環境變量的過程本質就是讓命令行通過“PATH”路徑找到“fromelf.exe”程序運行,默認運行“fromelf.exe”時它會輸出自己的幫助信息,這就是工具鏈的調用過程,MDK本質上也是如此調用工具鏈的,只是它集成爲GUI,相對於命令行對用戶更友好,畢竟上述配置環境變量的過程已經讓新手煩躁了。
38.3.2armcc、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 –O1等編譯選項,當我們修改MDK的編譯配置時,可看到該控制命令也會有相應的變化。然而我們無法在該編譯選項框中輸入命令,只能通過MDK提供的選項修改。
瞭解這些,我們就可以查詢具體的MDK編譯選項的具體信息了,如c/c++選項中的“Optimization:Leve 1(-O1)”是什麼功能呢?首先可瞭解到它是“-O”命令,命令後還帶個數字,查看MDK的幫助手冊,在armcc編譯器說明章節,可詳細瞭解。
在這裏插入圖片描述
利用MDK,我們一般不需要自己調用armcc工具,但經過這樣的過程就會對MDK有更深入的認識,面對它的各種編譯選項,就不會那麼頭疼了。
2.armlink
armlink是鏈接器,它把各個O文件鏈接組合在一起生成ELF格式的AXF文件,AXF文件是可執行的,下載器把該文件中的指令代碼下載到芯片後,該芯片就能運行程序了;利用armlink還可以控制程序存儲到指定的ROM或RAM地址。在MDK中可在“Option for Target->Linker”頁面配置armlink選項:
在這裏插入圖片描述
鏈接器默認是根據芯片類型的存儲器分佈來生成程序的,該存儲器分佈被記錄在工程裏的sct後綴的文件中,有特殊需要的話可自行編輯該文件,改變鏈接器的鏈接方式。
38.3.3armar、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”一欄。
【新建一個工具鏈演示文件夾】
執行fromelf --bin --output 流水燈.bin 流水燈.axf,可在該文件夾下得到一個bin文件;
執行fromelf --i32 --output 流水燈.hex 流水燈.axf,可在該文件夾下得到一個hex文件;
【在output文件夾下get一個bin文件】
DOS窗口中輸入:
fromelf --bin --output …\Output\流水燈.bin …\Output\流水燈.axf

在這裏插入圖片描述
38.4MDK工程的文件類型
除了上述編譯過程生成的文件,MDK工程中還包含了各種各樣的文件,下面我們統一介紹,MDK工程的常見文件類型如下表:
在這裏插入圖片描述
在這裏插入圖片描述
.axf可理解爲windos下的.exe
這些文件主要分爲MDK相關文件、源文件以及編譯、鏈接器生成的文件。我們以“多彩流水燈”工程爲例講解各種文件的功能。
38.4.1uvprojx、uvoptx、uvguix及ini工程文件
在工程的“Project”目錄下主要是MDK工程相關的文件:
在這裏插入圖片描述
1.uvprojx文件
uvprojx文件就是我們平時雙擊打開的工程文件,它記錄了整個工程的結構,如芯片類型、工程包含了哪些源文件等內容:
在這裏插入圖片描述
2.uvoptx文件
uvoptx文件記錄了工程的配置選項,如下載器的類型、變量跟蹤配置、斷點位置以及當前已打開的文件等等:
在這裏插入圖片描述
3. uvguix文件
uvguix文件記錄了MDK軟件的GUI佈局,如代碼編輯區窗口的大小、編譯輸出提示窗口的位置等等。
在這裏插入圖片描述
uvprojx、uvoptx及uvguix都是使用XML格式記錄的文件,若使用記事本打開可以看到XML代碼。而當使用MDK軟件打開時,它根據這些文件的XML記錄加載工程的各種參數,使得我們每次重新打開工程時,都能恢復上一次的工作環境。
在這裏插入圖片描述
這些工程參數都是當MDK正常退出時纔會被寫入保存,所以若MDK錯誤退出時(如使用Windows的任務管理器強制關閉),工程配置參數的最新更改是不會被記錄的,重新打開工程時要再次配置。
根據這幾個文件的記錄類型,可以知道uvprojx文件是最重要的,刪掉它我們就無法再正常打開工程了,而uvoptx及uvguix文件並不是必須的,可以刪除,重新使用MDK打開uvprojx工程文件後,會以默認參數重新創建uvoptx及uvguix文件。(所以當使用Git/SVN等代碼管理的時候,往往只保留uvprojx文件)
38.4.2源文件
源文件是工程中我們最熟悉的內容了,它們就是我們編寫的各種源代碼,MDK支持c、cpp、h、s、inc類型的源代碼文件,其中c、cpp分別是c/c++語言的源代碼,h是它們的頭文件,s是彙編文件,inc是彙編文件的頭文件,可使用“$include”語法包含。編譯器根據工程中的源文件最終生成機器碼。
38.4.3Output目錄下生成的文件
點擊MDK中的編譯按鈕,它會根據工程的配置及工程中的源文件輸出各種對象和列表文件,在工程的“Options for Targe->Output->Select Folder for Objects”和“Options for Targe->Listing->Select Folder for Listings”選項配置它們的輸出路徑:
在這裏插入圖片描述
在這裏插入圖片描述
1. lib庫文件
在某些場合下可能不希望提供給第三方一個可用的代碼庫,但不希望對方看到源碼,這個時候我們就可以把工程生成lib文件(Library file)提供給對方,在MDK中可配置“Options for Target->Create Library”選項把工程編譯成庫文件:
在這裏插入圖片描述
工程中生成可執行文件或庫文件只能二選一,默認編譯是生成可執行文件的,可執行文件即我們下載到芯片上直接運行的機器碼。
得到生成的*.lib文件後,可把它像C文件一樣添加到其它工程中,並在該工程調用lib提供的函數接口,除了不能看到*.lib文件的源碼,在應用方面它跟C源文件沒有區別。
2.dep、d依賴文件
.dep和.d文件(Dependency file)記錄的是工程或其它文件的依賴,主要記錄了引用的頭文件路徑,其中*.dep是整個工程的依賴,它以工程名命名,而*.d是單個源文件的依賴,它們以對應的源文件名命名。這些記錄使用文本格式存儲,我們可直接使用記事本打開:
在這裏插入圖片描述
在這裏插入圖片描述
3.crf交叉引用文件
.crf是交叉引用文件(Cross-Reference file),它主要包含了瀏覽信息(browse information),即源代碼中的宏定義、變量及函數的定義和聲明的位置。
我們在代碼編輯器中點擊“Go To Definition Of ‘xxxx’”可實現瀏覽跳轉,跳轉的時候,MDK就是通過
.crf文件查找出跳轉位置的。
在這裏插入圖片描述
通過配置MDK中的“Option for Target->Output->Browse Information”選項可以設置編譯時是否生成瀏覽信息,只有勾選該選項並編譯後,才能實現上面的瀏覽跳轉功能。
在這裏插入圖片描述
4. o、axf及elf文件
.o、.elf、.axf、.bin及*.hex文件都存儲了編譯器根據源代碼生成的機器碼,根據應用場合的不同,它們又有所區別。
(1)ELF文件說明
.o、.elf、.axf以及前面提到的lib文件都是屬於目標文件,它們都是使用ELF格式來存儲的,關於ELF格式的詳細內容請參考配套資料裏的《ELF文件格式》文檔瞭解,它講解的是Linux下的ELF格式,與MDK使用的格式有小區別,但大致相同。
ELF是Executable and Linking Format的縮寫,譯爲可執行鏈接格式,該格式用於記錄目標文件的內容。在Linux及Windows系統下都有使用該格式的文件(或類似格式)用於記錄應用程序的內容,告訴操作系統如何鏈接、加載及執行該應用程序。
elf文件主要有如下三種類型:
A可重定位的文件(Relocatable File),包含基礎代碼和數據,但它的代碼及數據都沒有指定絕對地址,因此它適合於與其他目標文件鏈接來創建可執行文件或者共享目標文件。 這種文件一般由編譯器根據源代碼生成。
例如MDK的armcc和armasm生成的
.o文件就是這一類,另外還有Linux的*.o 文件,Windows的 .obj文件。
B 可執行文件(Executable File) ,它包含適合於執行的程序,它內部組織的代碼數據都有固定的地址(或相對於基地址的偏移),系統可根據這些地址信息把程序加載到內存執行。這種文件一般由鏈接器根據可重定位文件鏈接而成,它主要是組織各個可重定位文件,給它們的代碼及數據一一打上地址標號,固定其在程序內部的位置,鏈接後,程序內部各種代碼及數據段不可再重定位(即不能再參與鏈接器的鏈接)。
例如MDK的armlink生成的
.elf及*.axf文件,(使用gcc編譯工具可生成*.elf文件,用armlink生成的是*.axf文件,.axf文件在.elf之外,增加了調試使用的信息,其餘區別不大,後面我們僅講解*.axf文件),另外還有Linux的/bin/bash文件,Windows的*.exe文件。
C共享目標文件(Shared Object File), 它的定義比較難理解,我們直接舉例。
MDK生成的*.lib文件就屬於共享目標文件,它可以繼續參與鏈接,加入到可執行文件之中。另外,Linux的.so,如/lib/ glibc-2.5.so,Windows的DLL都屬於這一類。
(2)o文件與axf文件的關係
根據上面的分類,我們瞭解到,.axf文件是由多個.o文件鏈接而成的,而*.o文件由相應的源文件編譯而成,一個源文件對應一個*.o文件。它們的關係如下:
在這裏插入圖片描述
圖中的中間代表的是armlink鏈接器,在它的右側是輸入鏈接器的*.o文件,左側是它輸出的axf文件。
可以看到,由於都使用ELF文件格式,
.o與*.axf文件的結構是類似的,它們包含ELF文件頭、程序頭、節區(section)以及節區頭部表。各個部分的功能說明如下:
AELF文件頭用來描述整個文件的組織,例如數據的大小端格式,程序頭、節區頭在文件中的位置等。
B程序頭告訴系統如何加載程序,例如程序主體存儲在本文件的哪個位置,程序的大小,程序要加載到內存什麼地址等等。MDK的可重定位文件*.o不包含這部分內容,因爲它還不是可執行文件,而armlink輸出的*.axf文件就包含該內容了。
C節區是*.o文件的獨立數據區域,它包含提供給鏈接視圖使用的大量信息,如指令(Code)、數據(RO、RW、ZI-data)、符號表(函數、變量名等)、重定位信息等,例如每個由C語言定義的函數在*.o文件中都會有一個獨立的節區;
D存儲在最後的節區頭則包含了本文件節區的信息,如節區名稱、大小等等。
總的來說,鏈接器把各個*.o文件的節區歸類、排列,根據目標器件的情況編排地址生成輸出,彙總到*.axf文件。例如:“多彩流水燈”工程中在“bsp_led.c”文件中有一個LED_GPIO_Config函數,而它內部調用了“stm32f10x_gpio.c”的GPIO_Init函數,經過armcc編譯後,LED_GPIO_Config及GPIO_Iint函數都成了指令代碼,分別存儲在bsp_led.o及stm32f10x_gpio.o文件中,這些指令在*.o文件都沒有指定地址,僅包含了內容、大小以及調用的鏈接信息,而經過鏈接器後,鏈接器給它們都分配了特定的地址,並且把地址根據調用指向鏈接起來。
在這裏插入圖片描述
(3)elf文件頭
接下來可以看看具體文件的內容,使用fromelf文件可以查看*.o、.axf及.lib文件的ELF信息。
使用命令行,切換到文件所在的目錄(工具鏈演示文件夾),輸入“fromelf –text –v bsp_led.o”命令,可控制輸出bsp_led.o的詳細信息,利用“-c、-z”等選項還可輸出反彙編指令文件、代碼及數據文件等信息,可親手嘗試一下。
在這裏插入圖片描述
爲了便於閱讀,使用fromelf指令生成了“多彩流水燈.axf”、“bsp_led”及“多彩流水燈.lib”的ELF信息,並已把這些信息保存在獨立的文件中,在“elf信息輸出”文件夾下可查看:
在這裏插入圖片描述
fromelf --text -v bsp_led.o > bsp_led_o_information.txt//生成文件到盤
直接打開“elf信息輸出”目錄下的bsp_led_o_elfInfo_v.txt文件,可看到如下內容:
在這裏插入圖片描述
值得一提的是在這個*.o文件中,它的ELF文件頭中告訴我們它的程序頭(Program header)大小爲“0 bytes”,且程序頭所在的文件位置偏移也爲“0”,這說明它是沒有程序頭的。
(4)程序頭
接下來打開“多彩流水燈_axf_elfInfo_v.txt”文件,查看工程的*.axf文件的詳細信息:
在這裏插入圖片描述
在這裏插入圖片描述
對比之下,可發現*.axf文件的ELF文件頭對程序頭的大小說明爲非0值,且給出了它在文件的偏移地址,在輸出信息之中,包含了程序頭的詳細信息。可看到,程序頭的“Physical Addr”描述了本程序要加載到的內存地址“0x0800 0000”,正好是STM32內部FLASH的首地址;“size in file”描述了本程序佔據的空間大小爲“3176 bytes”,它正是程序燒錄到FLASH中需要佔據的空間。
(5)節區頭
在ELF的原文件中,緊接着程序頭的一般是節區的主體信息,在節區主體信息之後是描述節區主體信息的節區頭,先來看看節區頭中的信息瞭解概況。通過對比*.o文件及*.axf文件的節區頭部信息,可以清楚地看出這兩種文件的區別。
在這裏插入圖片描述
這個節區頭描述的是該函數被編譯後的節區信息,其中包含了節區的類型(指令類型SHT_PROGBITS)、節區應存儲到的地址(0x00000000)、它主體信息在文件位置中的偏移(52)以及節區的大小(96 bytes)。
由於*.o文件是可重定位文件,所以它的地址並沒有被分配,是0x00000000(假如文件中還有其它函數,該函數生成的節區中,對應的地址描述也都是0)。當鏈接器鏈接時,根據這個節區頭信息,在文件中找到它的主體內容,並根據它的類型,把它加入到主程序中,並分配實際地址,鏈接後生成的*.axf文件,再來看看它的內容:
在這裏插入圖片描述
在這裏插入圖片描述
在*.axf文件中,主要包含了兩個節區,一個名爲ER_IROM1,一個名爲RW_IRAM1,這些節區頭信息中除了具有*.o文件中節區頭描述的節區類型、文件位置偏移、大小之外,更重要的是它們都有具體的地址描述,其中 ER_IROM1的地址爲0x08000000,而RW_IRAM1的地址爲0x20000000,它們正好是STM32內部FLASH及SRAM的首地址,對應節區的大小就是程序需要佔用FLASH及SRAM空間的實際大小。
也就是說,經過鏈接器後,它生成的*.axf文件已經彙總了其它*.o文件的所有內容,生成的ER_IROM1節區內容可直接寫入到STM32內部FLASH的具體位置。例如,前面*.o文件中的i.LED_GPIO_Config節區已經被加入到*.axf文件的ER_IROM1節區的某地址。
(6)節區主體及反彙編代碼
使用fromelf的-c選項可以查看部分節區的主體信息,對於指令節區,可根據其內容查看相應的反彙編代碼,打開“bsp_led_o_elfInfo_c.txt”文件可查看這些信息:
在這裏插入圖片描述
可看到,由於這是*.o文件,它的節區地址還是沒有分配的,基地址爲0x00000000,接着在LED_GPIO_Config標號之後,列出了一個表,表中包含了地址偏移、相應地址中的內容以及根據內容反彙編得到的指令。細看彙編指令,還可看到它包含了跳轉到RCC_APB2PeriphClockCmd及GPIO_Init標號的語句,而且這兩個跳轉語句原來的內容都是“f7fffffe”,這是因爲還*.o文件中並沒有RCC_APB2PeriphClockCmd及GPIO_Init標號的具體地址索引,在*.axf文件中,這是不一樣的。
接下來我們打開“多彩流水燈_axf_elfInfo_c.txt”文件,查看*.axf文件中,ER_IROM1節區中對應LED_GPIO_Config的內容:
在這裏插入圖片描述
可看到,除了基地址以及跳轉地址不同之外,LED_GPIO_Config中的內容跟*.o文件中的一樣。另外,由於*.o是獨立的文件,而*.axf是整個工程彙總的文件,所以在*.axf中包含了所有調用到*.o文件節區的內容。例如,在“bsp_led_o_elfInfo_c.txt”(bsp_led.o文件的反彙編信息)中不包含RCC_APB2PeriphClockCmd及GPIO_Init的內容,而在“流水燈_axf_elfInfo_c.txt” (流水燈.axf文件的反彙編信息)中則可找到它們的具體信息,且它們也有具體的地址空間。
在*.axf文件中,跳轉到RCC_APB2PeriphClockCmd及GPIO_Init標號的這兩個指令後都有註釋,分別是“; 0x8000980”及“; 0x8000408”,它們是這兩個標號所在的具體地址,而且這兩個跳轉語句的跟*.o中的也有區別,內容分別爲“f7fffefd”及“f7fffc34”(.o中的均爲f7fffffe)。這就是鏈接器鏈接的含義,它把不同.o中的內容鏈接起來了。
(7)分散加載代碼
學習至此,還有一個疑問,前面提到程序有存儲態及運行態,它們之間應有一個轉化過程,把存儲在FLASH中的RW-data數據拷貝至SRAM。然而我們的工程中並沒有編寫這樣的代碼,在彙編文件中也查不到該過程,芯片是如何知道FLASH的哪些數據應拷貝到SRAM的哪些區域呢?
通過查看“多彩流水燈_axf_elfInfo_c.txt”的反彙編信息,瞭解到程序中具有一段名爲“__scatterload”的分散加載代碼,它是由armlink鏈接器自動生成的。
在這裏插入圖片描述
這段分散加載代碼包含了拷貝過程(LDM複製指令),而LDM指令的操作數中包含了加載的源地址,這些地址中包含了內部FLASH存儲的RW-data數據。而 “__scatterload ”的代碼會被“__main”函數調用,__main在啓動文件中的“Reset_Handler”會被調用,因而,在主體程序執行前,已經完成了分散加載過程。
在這裏插入圖片描述
5.hex文件及bin文件
若編譯過程無誤,即可把工程生成前面對應的*.axf文件,而在MDK中使用下載器(DAP/JLINK/ULINK等)下載程序或仿真的時候,MDK調用的就是*.axf文件,它解釋該文件,然後控制下載器把*.axf中的代碼內容下載到STM32芯片對應的存儲空間,然後復位後芯片就開始執行代碼了。
然而,脫離了MDK或IAR等工具,下載器就無法直接使用*.axf文件下載代碼了,它們一般僅支持hex和bin格式的代碼數據文件。默認情況下MDK都不會生成hex及bin文件,需要配置工程選項或使用fromelf命令。
(1)生成hex文件
(2)生成bin文件
使用MDK生成bin文件需要使用fromelf命令,在MDK的“Options For Target->Users”中加入命令:“fromelf --bin --output …\Output\多彩流水燈.bin …\Output\多彩流水燈.axf”
該指令是根據本機及工程的配置而寫的,在不同的系統環境或不同的工程中,指令內容都不一樣,需要理解它,才能爲自己的工程定製指令,首先看看fromelf的幫助: 在這裏插入圖片描述
fromelf需要根據工程的*.axf文件輸入來轉換得到bin文件,所以在命令的輸入文件參數中要選擇本工程對應的*.axf文件,在MDK命令輸入欄中,我們把fromelf指令放置在“After Build/Rebuild”(工程構建完成後執行)一欄也是基於這個考慮,這樣設置後,工程構建完成生成了最新的*.axf文件,MDK再執行fromelf指令,從而得到最新的bin文件。
設置完成生成hex的選項或添加了生成bin的用戶指令後,點擊工程的編譯(build)按鈕,重新編譯工程,成功後可看到如下輸出,打開相應的目錄即可找到文件。
在這裏插入圖片描述
(3)hex文件格式
hex是Intel公司制定的一種使用ASCII文本記錄機器碼或常量數據的文件格式,這種文件常常用來記錄將要存儲到ROM中的數據,絕大多數下載器支持該格式。
一個hex文件由多條記錄組成,而每條記錄由五個部分組成,格式形“:llaaaatt[dd…]cc”,例如本“多彩流水燈”工程生成的hex文件前幾條記錄:
在這裏插入圖片描述
記錄的各個部分介紹如下:
“:” :每條記錄的開頭都使用冒號來表示一條記錄的開始;
ll :以16進制數表示這條記錄的主體數據區的長度(即後面[dd…]的長度);
aaaa:表示這條記錄中的內容應存放到FLASH中的起始地址;
tt:表示這條記錄的類型,它包含的各種類型如下;
在這裏插入圖片描述
dd:表示一個字節的數據,一條記錄中可以有多個字節數據,ll區表示了它有多少個字節的數據;
cc:表示本條記錄的校驗和,它是前面所有16進制數據 (除冒號外,兩個爲一組)的和對256取模運算的結果的補碼。
:02**0000**04**0800**F2
02:表示這條記錄數據區的長度爲2字節;
0000:表示這條記錄要存儲到的地址;
04:表示這是一條擴展線性地址記錄;
0800:由於這是一條擴展線性地址記錄,所以這部分表示地址的高16位,與前面的“0000”結合在一起,表示要擴展的線性地址爲“0x0800 0000”,這正好是STM32內部FLASH的首地址;
F2:表示校驗和,它的值爲(0x02+0x00+0x00+0x04+0x08+0x00)%256的值再取補碼。
在這裏插入圖片描述
(4)hex、bin及axf文件的區別與聯繫
bin、hex及axf文件都包含了指令代碼,但它們的信息豐富程度是不一樣的。
bin文件是最直接的代碼映像,它記錄的內容就是要存儲到FLASH的二進制數據(機器碼本質上就是二進制數據),在FLASH中是什麼形式它就是什麼形式,沒有任何輔助信息,包括大小端格式也沒有,因此下載器需要有針對芯片FLASH平臺的輔助文件才能正常下載(一般下載器程序會有匹配的這些信息);
hex文件是一種使用十六進制符號表示的代碼記錄,記錄了代碼應該存儲到FLASH的哪個地址,下載器可以根據這些信息輔助下載;
axf文件在前文已經解釋,它不僅包含代碼數據,還包含了工程的各種信息,因此它也是三個文件中最大的
6.htm靜態調用圖文件
在Output目錄下,有一個以工程文件命名的後綴爲*.bulid_log.htm及*.htm文件,如“多彩流水燈.bulid_log.htm”及“多彩流水燈.htm”,它們都可以使用瀏覽器打開。其*.build_log.htm是工程的構建過程日誌,而*.htm是鏈接器生成的靜態調用圖文件。
在靜態調用圖文件中包含了整個工程各種函數之間互相調用的關係圖,而且它還給出了靜態佔用最深的棧空間數量以及它對應的調用關係鏈。
在這裏插入圖片描述

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