MDK的编译过程及文件类型全解——(二)
前言:
為了方便查看博客,特意申請(qǐng)了一個(gè)公眾號(hào),附上二維碼,有興趣的朋友可以關(guān)注,和我一起討論學(xué)習(xí),一起享受技術(shù),一起成長(zhǎng)。
本文轉(zhuǎn)載自:第48章 MDK的編譯過(guò)程及文件類(lèi)型全解—零死角玩轉(zhuǎn)STM32-F429系列
1. MDK 相關(guān)文件
1.1 uvprojx 文件
uvprojx 文件就是我們平時(shí)雙擊打開(kāi)的工程文件,它記錄了整個(gè)工程的結(jié)構(gòu),如芯片類(lèi)型、工程包含了哪些源文件等內(nèi)容,見(jiàn)下圖 :
1.2 uvoptx 文件
uvoptx 文件記錄了工程的配置選項(xiàng),如下載器的類(lèi)型、變量跟蹤配置、斷點(diǎn)位置以及當(dāng)前已打開(kāi)的文件等等,見(jiàn)下圖:
1.3 uvguix 文件
uvguix文件記錄了 MDK 軟件的 GUI 布局,如代碼編輯區(qū)窗口的大小、編譯輸出提示窗口的位置等等。
uvprojx、uvoptx 及 uvguix 都是使用 XML 格式記錄的文件,若使用記事本打開(kāi)可以看到 XML 代碼,見(jiàn)下圖。而當(dāng)使用 MDK 軟件打開(kāi)時(shí),它根據(jù)這些文件的 XML 記錄加載工程的各種參數(shù),使得我們每次重新打開(kāi)工程時(shí),都能恢復(fù)上一次的工作環(huán)境。
這些工程參數(shù)都是當(dāng) MDK 正常退出時(shí)才會(huì)被寫(xiě)入保存,若 MDK 錯(cuò)誤退出時(shí)(如使用 Windows 的任務(wù)管理器強(qiáng)制關(guān)閉),工程配置參數(shù)的最新更改是不會(huì)被記錄的,重新打開(kāi)工程時(shí)要再次配置。根據(jù)這幾個(gè)文件的記錄類(lèi)型,可以知道 uvprojx 文件是最重要的,刪掉它我們就無(wú)法再正常打開(kāi)工程了,而 uvoptx 及 uvguix 文件并不是必須的,可以刪除,重新使用 MDK 打開(kāi) uvprojx 工程文件后,會(huì)以默認(rèn)參數(shù)重新創(chuàng)建 uvoptx 及 uvguix 文件。(所以當(dāng)使用 Git/SVN 等代碼管理的時(shí)候,往往只保留 uvproj x文件)
2. 源文件
源文件是工程中我們最熟悉的內(nèi)容了,它們就是我們編寫(xiě)的各種源代碼,MDK 支持 c、cpp、h、s、inc 類(lèi)型的源代碼文件,其中 c、cpp 分別是 c/c++ 語(yǔ)言的源代碼,h 是它們的頭文件,s 是匯編文件,inc 是匯編文件的頭文件,可使用 “$include” 語(yǔ)法包含。編譯器根據(jù)工程中的源文件最終生成機(jī)器碼。
3 . Output 目錄下生成的文件
點(diǎn)擊 MDK 中的編譯按鈕,它會(huì)根據(jù)工程的配置及工程中的源文件輸出各種對(duì)象和列表文件,在工程的 “Options for Targe->Output->Select Folder for Objects” 和 “Options for Targe->Listing->Select Folder for Listings” 選項(xiàng)配置它們的輸出路徑::Output 輸出路徑、Listing 輸出路徑。
編譯后 Output 和 Listing 目錄下生成的文件見(jiàn)下圖:
3.1 lib 庫(kù)文件
在某些場(chǎng)合下我們希望提供給第三方一個(gè)可用的代碼庫(kù),但不希望對(duì)方看到源碼,這個(gè)時(shí)候我們就可以把工程生成 lib 文件 (Library file) 提供給對(duì)方,在 MDK 中可配置 "Options for Target->Create Library"選 項(xiàng)把工程編譯成庫(kù)文件,見(jiàn)下圖:
工程中生成可執(zhí)行文件或庫(kù)文件只能二選一,默認(rèn)編譯是生成可執(zhí)行文件的,可執(zhí)行文件即我們下載到芯片上直接運(yùn)行的機(jī)器碼。
得到生成的 .lib 文件后,可把它像 C 文件一樣添加到其它工程中,并在該工程調(diào)用 lib 提供的函數(shù)接口,除了不能看到 .lib 文件的源碼,在應(yīng)用方面它跟 C 源文件沒(méi)有區(qū)別。
3.2 dep、d 依賴(lài)文件
.dep 和 .d 文件 (Dependency file) 記錄的是工程或其它文件的依賴(lài),主要記錄了引用的頭文件路徑,其中 .dep 是整個(gè)工程的依賴(lài),它以工程名命名,而 .d 是單個(gè)源文件的依賴(lài),它們以對(duì)應(yīng)的源文件名命名。這些記錄使用文本格式存儲(chǔ),我們可直接使用記事本打開(kāi),如下圖:
3.3 crf 交叉引用文件
.crf 是交叉引用文件 (Cross-Reference file),它主要包含了瀏覽信息 (browse information),即源代碼中的宏定義、變量及函數(shù)的定義和聲明的位置。
我們?cè)诖a編輯器中點(diǎn)擊 “Go To Definition Of ‘xxxx’” 可實(shí)現(xiàn)瀏覽跳轉(zhuǎn),見(jiàn)下圖,跳轉(zhuǎn)的時(shí)候,MDK 就是通過(guò) .crf 文件查找出跳轉(zhuǎn)位置的。
通過(guò)配置 MDK 中的 “Option for Target->Output->Browse Information” 選項(xiàng)可以設(shè)置編譯時(shí)是否生成瀏覽信息,見(jiàn)下圖。只有勾選該選項(xiàng)并編譯后,才能實(shí)現(xiàn)上面的瀏覽跳轉(zhuǎn)功能。
.crf 文件使用了特定的格式表示,直接用文本編輯器打開(kāi)會(huì)看到大部分亂碼,見(jiàn)下圖,這里不作深入研究。
3.4 o、axf及elf文件
.o、.elf、.axf、.bin 及 .hex 文件都存儲(chǔ)了編譯器根據(jù)源代碼生成的機(jī)器碼,根據(jù)應(yīng)用場(chǎng)合的不同,它們又有所區(qū)別。
3.4.1 ELF 文件說(shuō)明
.o、.elf、.axf 以及前面提到的 lib 文件都是屬于目標(biāo)文件,它們都是使用 ELF 格式來(lái)存儲(chǔ)的。
ELF 是Executable and Linking Format 的縮寫(xiě),譯為可執(zhí)行鏈接格式,該格式用于記錄目標(biāo)文件的內(nèi)容。在 Linux 及 Windows 系統(tǒng)下都有使用該格式的文件(或類(lèi)似格式)用于記錄應(yīng)用程序的內(nèi)容,告訴操作系統(tǒng)如何鏈接、加載及執(zhí)行該應(yīng)用程序。
目標(biāo)文件主要有如下三種類(lèi)型:
(1) 可重定位的文件 (Relocatable File):包含基礎(chǔ)代碼和數(shù)據(jù),但它的代碼及數(shù)據(jù)都沒(méi)有指定絕對(duì)地址,因此它適合于與其他目標(biāo)文件鏈接來(lái)創(chuàng)建可執(zhí)行文件或者共享目標(biāo)文件。 這種文件一般由編譯器根據(jù)源代碼生成。
例如 MDK 的 armcc 和 armasm 生成的 .o 文件就是這一類(lèi),另外還有 Linux 的 .o 文件,Windows 的 .obj 文件。
(2) 可執(zhí)行文件 (Executable File):它包含適合于執(zhí)行的程序,它內(nèi)部組織的代碼數(shù)據(jù)都有固定的地址(或相對(duì)于基地址的偏移),系統(tǒng)可根據(jù)這些地址信息把程序加載到內(nèi)存執(zhí)行。這種文件一般由鏈接器根據(jù)可重定位文件鏈接而成,它主要是組織各個(gè)可重定位文件,給它們的代碼及數(shù)據(jù)一一打上地址標(biāo)號(hào),固定其在程序內(nèi)部的位置,鏈接后,程序內(nèi)部各種代碼及數(shù)據(jù)段不可再重定位(即不能再參與鏈接器的鏈接)。
例如 MDK 的 armlink 生成的 .elf 及 .axf 文件,(使用 gcc 編譯工具可生成 .elf 文件,用 armlink 生成的是 .axf 文件, .axf 文件在 .elf 之外,增加了調(diào)試使用的信息,其余區(qū)別不大,后面我們僅講解 .axf 文件),另外還有 Linux 的 /bin/bash 文件,Windows 的 .exe 文件。
(3) 共享目標(biāo)文件 (Shared Object File): 它的定義比較難理解,我們直接舉例,MDK 生成的 .lib 文件就屬于共享目標(biāo)文件,它可以繼續(xù)參與鏈接,加入到可執(zhí)行文件之中。另外,Linux 的 .so,如 /lib/ glibc-2.5.so,Windows 的 DLL 都屬于這一類(lèi)。
3.4.1.1 o文件與axf文件的關(guān)系
根據(jù)上面的分類(lèi),我們了解到, .axf 文件是由多個(gè) .o 文件鏈接而成的,而 .o 文件由相應(yīng)的源文件編譯而成,一個(gè)源文件對(duì)應(yīng)一個(gè) .o 文件。它們的關(guān)系見(jiàn)下圖:
上圖中的中間代表的是 armlink 鏈接器,在它的右側(cè)是輸入鏈接器的 .o 文件,左側(cè)是它輸出的 .axf 文件。
可以看到,由于都使用 ELF 文件格式, .o 與 .axf 文件的結(jié)構(gòu)是類(lèi)似的,它們包含 ELF 文件頭、程序頭、節(jié)區(qū) (section) 以及節(jié)區(qū)頭部表。各個(gè)部分的功能說(shuō)明如下:
(1)ELF 文件頭用來(lái)描述整個(gè)文件的組織,例如數(shù)據(jù)的大小端格式,程序頭、節(jié)區(qū)頭在文件中的位置等。
(2)程序頭告訴系統(tǒng)如何加載程序,例如程序主體存儲(chǔ)在本文件的哪個(gè)位置,程序的大小,程序要加載到內(nèi)存什么地址等等。MDK 的可重定位文件 .o 不包含這部分內(nèi)容,因?yàn)樗€不是可執(zhí)行文件,而 armlink 輸出的 .axf 文件就包含該內(nèi)容了。
(3)節(jié)區(qū)是 .o 文件的獨(dú)立數(shù)據(jù)區(qū)域,它包含提供給鏈接視圖使用的大量信息,如指令 (Code)、數(shù)據(jù) (RO、RW、ZI-data)、符號(hào)表(函數(shù)、變量名等)、重定位信息等,例如每個(gè)由 C 語(yǔ)言定義的函數(shù)在 .o 文件中都會(huì)有一個(gè)獨(dú)立的節(jié)區(qū);
(4)存儲(chǔ)在最后的節(jié)區(qū)頭則包含了本文件節(jié)區(qū)的信息,如節(jié)區(qū)名稱(chēng)、大小等等。
總的來(lái)說(shuō),鏈接器把各個(gè) .o 文件的節(jié)區(qū)歸類(lèi)、排列,根據(jù)目標(biāo)器件的情況編排地址生成輸出,匯總到 .axf 文件。例如,見(jiàn)下圖,“多彩流水燈” 工程中在 “bsp_led.c” 文件中有一個(gè) LED_GPIO_Config 函數(shù),而它內(nèi)部調(diào)用了 “stm32f4xx_gpio.c” 的 GPIO_Init 函數(shù),經(jīng)過(guò) armcc 編譯后,LED_GPIO_Config 及 GPIO_Iint 函數(shù)都成了指令代碼,分別存儲(chǔ)在 bsp_led.o 及 stm32f4xx_gpio.o 文件中,這些指令在 .o 文件都沒(méi)有指定地址,僅包含了內(nèi)容、大小以及調(diào)用的鏈接信息,而經(jīng)過(guò)鏈接器后,鏈接器給它們都分配了特定的地址,并且把地址根據(jù)調(diào)用指向鏈接起來(lái)。
3.4.1.2 ELF 文件頭
接下來(lái)我們看看具體文件的內(nèi)容,使用 fromelf 文件可以查看 .o、 .axf 及 .lib 文件的 ELF 信息。
使用命令行,切換到文件所在的目錄,輸入 “fromelf –text –v bsp_led.o” 命令,可控制輸出 bsp_led.o 的詳細(xì)信息,見(jiàn)下圖。利用 “-c、-z” 等選項(xiàng)還可輸出反匯編指令文件、代碼及數(shù)據(jù)文件等信息,請(qǐng)親手嘗試一下。
為了便于閱讀,已使用 fromelf 指令生成了 “多彩流水燈.axf”、“bsp_led” 及"多彩流水燈 .lib" 的 ELF 信息,并已把這些信息保存在獨(dú)立的文件中,參見(jiàn)下表。
========================================================================** ELF Header InformationFile Name:.\bsp_led.o //bsp_led.o文件Machine class: ELFCLASS32 (32-bit) //32位機(jī)Data encoding: ELFDATA2LSB (Little endian) //小端格式Header version: EV_CURRENT (Current version)Operating System ABI: noneABI Version: 0File Type: ET_REL (Relocatable object) (1) //可重定位文件類(lèi)型Machine: EM_ARM (ARM)Entry offset (in SHF_ENTRYSECT section): 0x00000000Flags: None (0x05000000)ARM ELF revision: 5 (ABI version 2)Built withComponent: ARM Compiler 5.06 (build 20) Tool: armasm [4d35a2]Component: ARM Compiler 5.06 (build 20) Tool: armlink [4d35a3]Header size: 52 bytes (0x34)Program header entry size: 0 bytes (0x0) //程序頭大小Section header entry size: 40 bytes (0x28)Program header entries: 0Section header entries: 246Program header offset: 0 (0x00000000) //程序頭在文件中的位置(沒(méi)有程序頭)Section header offset: 507224 (0x0007bd58) //節(jié)區(qū)頭在文件中的位置Section header string table index: 243=====================================================================在上述代碼中已加入了部分注釋,解釋了相應(yīng)項(xiàng)的意義,值得一提的是在這個(gè) .o 文件中,它的 ELF 文件頭中告訴我們它的程序頭 (Program header) 大小為 “0 bytes”,且程序頭所在的文件位置偏移也為 “0”,這說(shuō)明它是沒(méi)有程序頭的。
3.4.1.3 程序頭
===================================================================** ELF Header InformationFile Name:.\多彩流水燈.axf //多彩流水燈.axf 文件Machine class: ELFCLASS32 (32-bit) //32位機(jī)Data encoding: ELFDATA2LSB (Little endian) //小端格式Header version: EV_CURRENT (Current version)Operating System ABI: noneABI Version: 0File Type: ET_EXEC (Executable) (2) //可執(zhí)行文件類(lèi)型Machine: EM_ARM (ARM)Image Entry point: 0x080001adFlags: EF_ARM_HASENTRY + EF_ARM_ABI_FLOAT_SOFT (0x05000202)ARM ELF revision: 5 (ABI version 2)Conforms to Soft float procedure-call standardBuilt withComponent: ARM Compiler 5.06 (build 20) Tool: armasm [4d35a2]Component: ARM Compiler 5.06 (build 20) Tool: armlink [4d35a3]Header size: 52 bytes (0x34)Program header entry size: 32 bytes (0x20)Section header entry size: 40 bytes (0x28)Program header entries: 1Section header entries: 15Program header offset: 335252 (0x00051d94) //程序頭在文件中的位置Section header offset: 335284 (0x00051db4) //節(jié)區(qū)頭在文件中的位置Section header string table index: 14=================================================================** Program header #0Type : PT_LOAD (1) //表示這是可加載的內(nèi)容File Offset : 52 (0x34) //在文件中的偏移Virtual Addr : 0x08000000 //虛擬地址(此處等于物理地址)Physical Addr : 0x08000000 //物理地址Size in file : 1456 bytes (0x5b0) //程序在文件中占據(jù)的大小Size in memory: 2480 bytes (0x9b0) //若程序加載到內(nèi)存,占據(jù)的內(nèi)存空間Flags : PF_X + PF_W + PF_R + PF_ARM_ENTRY (0x80000007)Alignment : 8 //地址對(duì)齊===============================================================對(duì)比之下,可發(fā)現(xiàn) .axf 文件的 ELF 文件頭對(duì)程序頭的大小說(shuō)明為非 0 值,且給出了它在文件的偏移地址,在輸出信息之中,包含了程序頭的詳細(xì)信息。可看到,程序頭的 “Physical Addr” 描述了本程序要加載到的內(nèi)存地址 “0x0800 0000”,正好是 STM3 2內(nèi)部 FLASH 的首地址;“size in file” 描述了本程序占據(jù)的空間大小為 “1456 bytes”,它正是程序燒錄到 FLASH 中需要占據(jù)的空間。
3.4.1.4 節(jié)區(qū)頭
在 ELF 的原文件中,緊接著程序頭的一般是節(jié)區(qū)的主體信息,在節(jié)區(qū)主體信息之后是描述節(jié)區(qū)主體信息的節(jié)區(qū)頭.
====================================// Section #4Name : i.LED_GPIO_Config //節(jié)區(qū)名//此節(jié)區(qū)包含程序定義的信息,其格式和含義都由程序來(lái)解釋。Type : SHT_PROGBITS (0x00000001)//此節(jié)區(qū)在進(jìn)程執(zhí)行過(guò)程中占用內(nèi)存。節(jié)區(qū)包含可執(zhí)行的機(jī)器指令。Flags :SHF_ALLOC + SHF_EXECINSTR (0x00000006)Addr : 0x00000000 //地址File Offset : 68 (0x44) //在文件中的偏移Size : 116 bytes (0x74) //大小Link : SHN_UNDEFInfo : 0Alignment : 4 //字節(jié)對(duì)齊Entry Size : 0====================================這個(gè)節(jié)區(qū)的名稱(chēng)為 LED_GPIO_Config,它正好是我們?cè)?bsp_led.c 文件中定義的函數(shù)名,這個(gè)節(jié)區(qū)頭描述的是該函數(shù)被編譯后的節(jié)區(qū)信息,其中包含了節(jié)區(qū)的類(lèi)型(指令類(lèi)型)、節(jié)區(qū)應(yīng)存儲(chǔ)到的地址 (0x00000000)、它主體信息在文件位置中的偏移 (68) 以及節(jié)區(qū)的大小 (116 bytes)。
由于 .o 文件是可重定位文件,所以它的地址并沒(méi)有被分配,是 0x00000000(假如文件中還有其它函數(shù),該函數(shù)生成的節(jié)區(qū)中,對(duì)應(yīng)的地址描述也都是 0)。當(dāng)鏈接器鏈接時(shí),根據(jù)這個(gè)節(jié)區(qū)頭信息,在文件中找到它的主體內(nèi)容,并根據(jù)它的類(lèi)型,把它加入到主程序中,并分配實(shí)際地址,鏈接后生成的 .axf 文件,我們?cè)賮?lái)看看它的內(nèi)容:
========================================================================** Section #1Name : ER_IROM1 //節(jié)區(qū)名//此節(jié)區(qū)包含程序定義的信息,其格式和含義都由程序來(lái)解釋。Type : SHT_PROGBITS (0x00000001)//此節(jié)區(qū)在進(jìn)程執(zhí)行過(guò)程中占用內(nèi)存。節(jié)區(qū)包含可執(zhí)行的機(jī)器指令Flags : SHF_ALLOC + SHF_EXECINSTR (0x00000006)Addr : 0x08000000 //地址File Offset : 52 (0x34)Size : 1456 bytes (0x5b0) //大小Link : SHN_UNDEFInfo : 0Alignment : 4Entry Size : 0====================================** Section #2Name : RW_IRAM1 //節(jié)區(qū)名//包含將出現(xiàn)在程序的內(nèi)存映像中的為初始//化數(shù)據(jù)。根據(jù)定義,當(dāng)程序開(kāi)始執(zhí)行,系統(tǒng)//將把這些數(shù)據(jù)初始化為 0。Type : SHT_NOBITS (0x00000008)//此節(jié)區(qū)在進(jìn)程執(zhí)行過(guò)程中占用內(nèi)存。節(jié)區(qū)包含進(jìn)程執(zhí)行過(guò)程中將可寫(xiě)的數(shù)據(jù)。Flags : SHF_ALLOC + SHF_WRITE (0x00000003)Addr : 0x20000000 //地址File Offset : 1508 (0x5e4)Size : 1024 bytes (0x400) //大小Link : SHN_UNDEFInfo : 0Alignment : 8Entry Size : 0====================================在 .axf 文件中,主要包含了兩個(gè)節(jié)區(qū),一個(gè)名為 ER_IROM1,一個(gè)名為 RW_IRAM1,這些節(jié)區(qū)頭信息中除了具有 .o 文件中節(jié)區(qū)頭描述的節(jié)區(qū)類(lèi)型、文件位置偏移、大小之外,更重要的是它們都有具體的地址描述,其中 ER_IROM1 的地址為 0x08000000,而 RW_IRAM1 的地址為 0x20000000,它們正好是內(nèi)部 FLASH 及 SRAM 的首地址,對(duì)應(yīng)節(jié)區(qū)的大小就是程序需要占用 FLASH 及 SRAM 空間的實(shí)際大小。
也就是說(shuō),經(jīng)過(guò)鏈接器后,它生成的 .axf 文件已經(jīng)匯總了其它 .o 文件的所有內(nèi)容,生成的 ER_IROM1 節(jié)區(qū)內(nèi)容可直接寫(xiě)入到 STM32 內(nèi)部 FLASH 的具體位置。例如,前面 .o 文件中的 i.LED_GPIO_Config 節(jié)區(qū)已經(jīng)被加入到 .axf 文件的ER_IROM1 節(jié)區(qū)的某地址。
3.4.1.5 節(jié)區(qū)主體及反匯編代碼
使用 fromelf 的 -c 選項(xiàng)可以查看部分節(jié)區(qū)的主體信息,對(duì)于指令節(jié)區(qū),可根據(jù)其內(nèi)容查看相應(yīng)的反匯編代碼。
//.o文件的LED_GPIO_Config節(jié)區(qū)及反匯編代碼** Section #4 'i.LED_GPIO_Config' (SHT_PROGBITS) [SHF_ALLOC + SHF_EXECINSTR]Size : 116 bytes (alignment 4)Address: 0x00000000$ti.LED_GPIO_ConfigLED_GPIO_Config// 地址內(nèi)容 (ASCII碼) 內(nèi)容對(duì)應(yīng)的代碼// (無(wú)意義)0x00000000: e92d41fc -..A PUSH {r2-r8,lr}0x00000004: 2101 .! MOVS r1,#10x00000006: 2088 . MOVS r0,#0x880x00000008: f7fffffe .... BL RCC_AHB1PeriphClockCmd0x0000000c: f44f6580 O..e MOV r5,#0x4000x00000010: 9500 .. STR r5,[sp,#0]0x00000012: 2101 .! MOVS r1,#10x00000014: f88d1004 .... STRB r1,[sp,#4]0x00000018: 2000 . MOVS r0,#00x0000001a: f88d0006 .... STRB r0,[sp,#6]0x0000001e: f88d1007 .... STRB r1,[sp,#7]0x00000022: f88d0005 .... STRB r0,[sp,#5]0x00000026: 4f11 .O LDR r7,[pc,#68] ;0x00000028: 4669 iF MOV r1,sp0x0000002a: 4638 8F MOV r0,r70x0000002c: f7fffffe .... BL GPIO_Init0x00000030: 006c l. LSLS r4,r5,#1/*....以下省略**/可看到,由于這是 .o 文件,它的節(jié)區(qū)地址還是沒(méi)有分配的,基地址為 0x00000000,接著在 LED_GPIO_Config 標(biāo)號(hào)之后,列出了一個(gè)表,表中包含了地址偏移、相應(yīng)地址中的內(nèi)容以及根據(jù)內(nèi)容反匯編得到的指令。細(xì)看匯編指令,還可看到它包含了跳轉(zhuǎn)到 RCC_AHB1PeriphClockCmd 及 GPIO_Init 標(biāo)號(hào)的語(yǔ)句,而且這兩個(gè)跳轉(zhuǎn)語(yǔ)句原來(lái)的內(nèi)容都是 “f7fffffe”,這是因?yàn)檫€ .o 文件中并沒(méi)有 RCC_AHB1PeriphClockCmd 及 GPIO_Init 標(biāo)號(hào)的具體地址索引,在 .axf 文件中,這是不一樣的。
//.axf文件的LED_GPIO_Config反匯編代碼i.LED_GPIO_ConfigLED_GPIO_Config0x080002a4: e92d41fc -..A PUSH {r2-r8,lr}0x080002a8: 2101 .! MOVS r1,#10x080002aa: 2088 . MOVS r0,#0x880x080002ac: f000f838 ..8. BL RCC_AHB1PeriphClockCmd ; 0x80003200x080002b0: f44f6580 O..e MOV r5,#0x4000x080002b4: 9500 .. STR r5,[sp,#0]0x080002b6: 2101 .! MOVS r1,#10x080002b8: f88d1004 .... STRB r1,[sp,#4]0x080002bc: 2000 . MOVS r0,#00x080002be: f88d0006 .... STRB r0,[sp,#6]0x080002c2: f88d1007 .... STRB r1,[sp,#7]0x080002c6: f88d0005 .... STRB r0,[sp,#5]0x080002ca: 4f11 .O LDR r7,[pc,#68] ; [0x8000310] = 0x40021c000x080002cc: 4669 iF MOV r1,sp0x080002ce: 4638 8F MOV r0,r70x080002d0: f7ffffa5 .... BL GPIO_Init ; 0x800021e0x080002d4: 006c l. LSLS r4,r5,#1/*....以下省略**/可看到,除了基地址以及跳轉(zhuǎn)地址不同之外,LED_GPIO_Config 中的內(nèi)容跟 .o 文件中的一樣。另外,由于 .o 是獨(dú)立的文件,而 .axf 是整個(gè)工程匯總的文件,所以在 .axf 中包含了所有調(diào)用到 .o 文件節(jié)區(qū)的內(nèi)容。
在 .axf 文件中,跳轉(zhuǎn)到 RCC_AHB1PeriphClockCmd 及 GPIO_Init 標(biāo)號(hào)的這兩個(gè)指令后都有注釋,分別是 “; 0x8000320” 及 “; 0x800021e”,它們是這兩個(gè)標(biāo)號(hào)所在的具體地址,而且這兩個(gè)跳轉(zhuǎn)語(yǔ)句的跟 .o 中的也有區(qū)別,內(nèi)容分別為 “f000f838e” 及 “f7ffffa5”( .o中的均為f7fffffe)。這就是鏈接器鏈接的含義,它把不同 .o 中的內(nèi)容鏈接起來(lái)了。
3.4.1.6 分散加載代碼
學(xué)習(xí)至此,還有一個(gè)疑問(wèn),前面提到程序有存儲(chǔ)態(tài)及運(yùn)行態(tài),它們之間應(yīng)有一個(gè)轉(zhuǎn)化過(guò)程,把存儲(chǔ)在 FLASH 中的 RW-data 數(shù)據(jù)拷貝至 SRAM。然而我們的工程中并沒(méi)有編寫(xiě)這樣的代碼,在匯編文件中也查不到該過(guò)程,芯片是如何知道 FLASH 的哪些數(shù)據(jù)應(yīng)拷貝到 SRAM 的哪些區(qū)域呢?程序中具有一段名為 “__scatterload” 的分散加載代碼,它是由 armlink 鏈接器自動(dòng)生成的.
//分散加載代碼.text__scatterload__scatterload_rt20x080001e4: 4c06 .L LDR r4,[pc,#24] ; [0x8000200] = 0x80005a00x080001e6: 4d07 .M LDR r5,[pc,#28] ; [0x8000204] = 0x80005b00x080001e8: e006 .. B 0x80001f8 ; __scatterload + 200x080001ea: 68e0 .h LDR r0,[r4,#0xc]0x080001ec: f0400301 @... ORR r3,r0,#10x080001f0: e8940007 .... LDM r4,{r0-r2}0x080001f4: 4798 .G BLX r30x080001f6: 3410 .4 ADDS r4,r4,#0x100x080001f8: 42ac .B CMP r4,r50x080001fa: d3f6 .. BCC 0x80001ea ; __scatterload + 60x080001fc: f7ffffda .... BL __main_after_scatterload ; 0x80001b4$d0x08000200: 080005a0 .... DCD 1342191680x08000204: 080005b0 .... DCD 134219184這段分散加載代碼包含了拷貝過(guò)程( LDM 復(fù)制指令),而 LDM 指令的操作數(shù)中包含了加載的源地址,這些地址中包含了內(nèi)部 FLASH 存儲(chǔ)的 RW-data 數(shù)據(jù)。而 "__scatterload " 的代碼會(huì)被 “__main” 函數(shù)調(diào)用,__main 在啟動(dòng)文件中的 “Reset_Handler” 會(huì)被調(diào)用,因而,在主體程序執(zhí)行前,已經(jīng)完成了分散加載過(guò)程。
_main_main_stk0x080001ac: f8dfd00c .... LDR sp,__lit__00000000 ; [0x80001bc] = 0x20000400.ARM.Collect$$$$00000004_main_scatterload0x080001b0: f000f818 .... BL __scatterload ; 0x80001e43.5 hex文件及bin文件
若編譯過(guò)程無(wú)誤,即可把工程生成前面對(duì)應(yīng)的 .axf 文件,而在 MDK 中使用下載器 (DAP/JLINK/ULINK 等)下載程序或仿真的時(shí)候,MDK 調(diào)用的就是 .axf 文件,它解釋該文件,然后控制下載器把 .axf 中的代碼內(nèi)容下載到 STM32 芯片對(duì)應(yīng)的存儲(chǔ)空間,然后復(fù)位后芯片就開(kāi)始執(zhí)行代碼了。
然而,脫離了 MDK 或 IAR 等工具,下載器就無(wú)法直接使用 .axf 文件下載代碼了,它們一般僅支持 hex 和 bin 格式的代碼數(shù)據(jù)文件。默認(rèn)情況下 MDK 都不會(huì)生成 hex 及 bin 文件,需要配置工程選項(xiàng)或使用 fromelf 命令。
3.5.1 生成hex文件
生成 hex 文件的配置比較簡(jiǎn)單,在 “Options for Target->Output->Create Hex File” 中勾選該選項(xiàng),然后編譯工程即可,見(jiàn)下圖 :
3.5.2 生成bin文件
使用 MDK 生成 bin 文件需要使用 fromelf 命令,在 MDK 的 “Options For Target->Users” 中加入下圖的命令。
圖中的指令內(nèi)容為:
“fromelf --bin --output …\Output\多彩流水燈.bin …\Output\多彩流水燈.axf”
該指令是根據(jù)本機(jī)及工程的配置而寫(xiě)的,在不同的系統(tǒng)環(huán)境或不同的工程中,指令內(nèi)容都不一樣,我們需要理解它,才能為自己的工程定制指令,首先看看 fromelf 的幫助,見(jiàn)下圖:
我們?cè)?MDK 輸入的指令格式是遵守 fromelf 幫助里的指令格式說(shuō)明的,其格式為:
“fromelf [options] input_file”
其中 optinos 是指令選項(xiàng),一個(gè)指令支持輸入多個(gè)選項(xiàng),每個(gè)選項(xiàng)之間使用空格隔開(kāi),我們的實(shí)例中使用 “–bin” 選項(xiàng)設(shè)置輸出 bin 文件,使用 “–output file” 選項(xiàng)設(shè)置輸出文件的名字為 “…\Output\多彩流水燈.bin”,這個(gè)名字是一個(gè)相對(duì)路徑格式,如果不了解如何使用 “…” 表示路徑,可使用 MDK 命令輸入框后面的文件夾圖標(biāo)打開(kāi)文件瀏覽器選擇文件,在命令的最后使用 “…\Output\多彩流水燈.axf” 作為命令的輸入文件。具體的格式分解見(jiàn)下圖:
fromelf 需要根據(jù)工程的 .axf 文件輸入來(lái)轉(zhuǎn)換得到 bin 文件,所以在命令的輸入文件參數(shù)中要選擇本工程對(duì)應(yīng)的 .axf 文件,在 MDK 命令輸入欄中,我們把 fromelf 指令放置在 “After Build/Rebuild” (工程構(gòu)建完成后執(zhí)行)一欄也是基于這個(gè)考慮,這樣設(shè)置后,工程構(gòu)建完成生成了最新的 .axf 文件,MDK 再執(zhí)行 fromelf 指令,從而得到最新的 bin 文件。
設(shè)置完成生成 hex 的選項(xiàng)或添加了生成 bin 的用戶(hù)指令后,點(diǎn)擊工程的編譯 (build)按鈕,重新編譯工程,成功后可看到下圖中的輸出。打開(kāi)相應(yīng)的目錄即可找到文件,若找不到 bin 文件,請(qǐng)查看提示輸出欄執(zhí)行指令的信息,根據(jù)信息改正 fromelf 指令。
其中 bin 文件是純二進(jìn)制數(shù)據(jù),無(wú)特殊格式,接下來(lái)我們了解一下 hex 文件格式。
3.5.3 hex文件格式
hex 是 Intel 公司制定的一種使用 ASCII 文本記錄機(jī)器碼或常量數(shù)據(jù)的文件格式,這種文件常常用來(lái)記錄將要存儲(chǔ)到 ROM 中的數(shù)據(jù),絕大多數(shù)下載器支持該格式。
一個(gè) hex 文件由多條記錄組成,而每條記錄由五個(gè)部分組成,格式形如 “: ll aaaatt[dd…]cc”,例如本"多彩流水燈"工程生成的 hex 文件前幾條記錄如下:
:020000040800F2:1000000000040020C10100081B030008A30200082F:100010001903000809020008690400080000000034:100020000000000000000000000000003D03000888:100030000B020008000000001D0300081504000862:10004000DB010008DB010008DB010008DB01000820記錄的各個(gè)部分介紹如下:
(1) “:” :每條記錄的開(kāi)頭都使用冒號(hào)來(lái)表示一條記錄的開(kāi)始;
(2) ll :以 16 進(jìn)制數(shù)表示這條記錄的主體數(shù)據(jù)區(qū)的長(zhǎng)度(即后面[dd…]的長(zhǎng)度);
(3) aaaa:表示這條記錄中的內(nèi)容應(yīng)存放到 FLASH 中的起始地址;
(4) tt:表示這條記錄的類(lèi)型,它包含中的各種類(lèi)型;
(5) dd:表示一個(gè)字節(jié)的數(shù)據(jù),一條記錄中可以有多個(gè)字節(jié)數(shù)據(jù),ll 區(qū)表示了它有多少個(gè)字節(jié)的數(shù)據(jù);
(6) cc:表示本條記錄的校驗(yàn)和,它是前面所有 16 進(jìn)制數(shù)據(jù) (除冒號(hào)外,兩個(gè)為一組)的和對(duì) 256 取模運(yùn)算的結(jié)果的補(bǔ)碼。
例如,上面的第一條記錄解釋如下:
(1) 02:表示這條記錄數(shù)據(jù)區(qū)的長(zhǎng)度為 2 字節(jié);
(2) 0000:表示這條記錄要存儲(chǔ)到的地址;
(3) 04:表示這是一條擴(kuò)展線(xiàn)性地址記錄;
(4) 0800:由于這是一條擴(kuò)展線(xiàn)性地址記錄,所以這部分表示地址的高 16 位,與前面的 “0000” 結(jié)合在一起,表示要擴(kuò)展的線(xiàn)性地址為 “0x0800 0000”,這正好是 STM32 內(nèi)部 FLASH 的首地址;
(5) F2:表示校驗(yàn)和,它的值為 (0x02+0x00+0x00+0x04+0x08+0x00) % 256 的值再取補(bǔ)碼。
再來(lái)看第二條記錄:
(1) 10:表示這條記錄數(shù)據(jù)區(qū)的長(zhǎng)度為 16 字節(jié);
(2) 0000:表示這條記錄所在的地址,與前面的擴(kuò)展記錄結(jié)合,表示這條記錄要存儲(chǔ)的 FLASH 首地址為(0x0800 0000+0x0000);
(3) 00:表示這是一條數(shù)據(jù)記錄,數(shù)據(jù)區(qū)的是地址;
(4) 00040020C10100081B030008A3020008:這是要按地址存儲(chǔ)的數(shù)據(jù);
(5) 2F:校驗(yàn)和
為了更清楚地對(duì)比 bin、hex 及 axf 文件的差異,我們來(lái)查看這些文件內(nèi)部記錄的信息來(lái)進(jìn)行對(duì)比。
3.5.4 hex、bin及axf文件的區(qū)別與聯(lián)系
bin、hex 及 axf 文件都包含了指令代碼,但它們的信息豐富程度是不一樣的。
(1) bin 文件是最直接的代碼映像,它記錄的內(nèi)容就是要存儲(chǔ)到 FLASH 的二進(jìn)制數(shù)據(jù)(機(jī)器碼本質(zhì)上就是二進(jìn)制數(shù)據(jù)),在 FLASH 中是什么形式它就是什么形式,沒(méi)有任何輔助信息,包括大小端格式也沒(méi)有,因此下載器需要有針對(duì)芯片 FLASH 平臺(tái)的輔助文件才能正常下載(一般下載器程序會(huì)有匹配的這些信息);
(2) hex 文件是一種使用十六進(jìn)制符號(hào)表示的代碼記錄,記錄了代碼應(yīng)該存儲(chǔ)到 FLASH 的哪個(gè)地址,下載器可以根據(jù)這些信息輔助下載;
(3) axf 文件在前文已經(jīng)解釋,它不僅包含代碼數(shù)據(jù),還包含了工程的各種信息,因此它也是三個(gè)文件中最大的。
同一個(gè)工程生成的 bin、hex 及 axf 文件的大小見(jiàn)下圖:
實(shí)際上,這個(gè)工程要燒寫(xiě)到 FLASH 的內(nèi)容總大小為 1456 字節(jié),然而在 Windows 中查看的 bin 文件卻比它大( bin 文件是 FLASH 的代碼映像,大小應(yīng)一致),這是因?yàn)?Windows 文件顯示單位的原因,使用右鍵查看文件的屬性,可以查看它實(shí)際記錄內(nèi)容的大小,見(jiàn)下圖:
接下來(lái)我們打開(kāi)本工程的 “多彩流水燈.bin”、“多彩流水燈 .hex “及由"多彩流水燈.axf” 使用 fromelf 工具輸出的反匯編文件"多彩流水燈 _axf_elfInfo_c.txt” 文件,清晰地對(duì)比它們的差異,見(jiàn)下圖。如果您想要親自閱讀自己電腦上的 bin 文件,推薦使用 sublime 軟件打開(kāi),它可以把二進(jìn)制數(shù)以 ASCII 碼呈現(xiàn)出來(lái),便于閱讀。
在 hex 文件中包含了地址信息以及地址中的內(nèi)容,而在 bin 文件中僅包含了內(nèi)容,連存儲(chǔ)的地址信息都沒(méi)有。觀(guān)察可知,bin、hex及axf 文件中的數(shù)據(jù)內(nèi)容都是相同的,它們存儲(chǔ)的都是機(jī)器碼。這就是它們?nèi)贾g的區(qū)別與聯(lián)系。
由于文件中存儲(chǔ)的都是機(jī)器碼,見(jiàn)下圖,該圖是根據(jù) axf 文件的 GPIO_Init 函數(shù)的機(jī)器碼,在 bin 及 hex 中找到的對(duì)應(yīng)位置。所以經(jīng)驗(yàn)豐富的人是有可能從 bin 或 hex 文件中恢復(fù)出匯編代碼的,只是成本較高,但不是不可能。
如果芯片沒(méi)有做任何加密措施,使用下載器可以直接從芯片讀回它存儲(chǔ)在 FLASH 中的數(shù)據(jù),從而得到 bin 映像文件,根據(jù)芯片型號(hào)還原出部分代碼即可進(jìn)行修改,甚至不用修改代碼,直接根據(jù)目標(biāo)產(chǎn)品的硬件 PCB,抄出一樣的板子,再把 bin 映像下載芯片,直接山寨出目標(biāo)產(chǎn)品,所以在實(shí)際的生產(chǎn)中,一定要注意做好加密措施。由于 axf 文件中含有大量的信息,且直接使用 fromelf 即可反匯編代碼,所以更不要隨便泄露 axf 文件。lib 文件也能反使用 fromelf 文件反匯編代碼,不過(guò)它不能還原出 C 代碼,由于 lib 文件的主要目的是為了保護(hù) C 源代碼,也算是達(dá)到了它的要求。
3.5.5 htm靜態(tài)調(diào)用圖文件
Output 目錄下,有一個(gè)以工程文件命名的后綴為 .bulid_log.htm 及 .htm 文件,如"多彩流水燈 .bulid_log.htm" 及"多彩流水燈 .htm",它們都可以使用瀏覽器打開(kāi)。其中 .build_log.htm 是工程的構(gòu)建過(guò)程日志,而 .htm 是鏈接器生成的靜態(tài)調(diào)用圖文件。
在靜態(tài)調(diào)用圖文件中包含了整個(gè)工程各種函數(shù)之間互相調(diào)用的關(guān)系圖,而且它還給出了靜態(tài)占用最深的棧空間數(shù)量以及它對(duì)應(yīng)的調(diào)用關(guān)系鏈。
該文件說(shuō)明了本工程的靜態(tài)棧空間最大占用 56 字節(jié) (Maximum Stack Usage:56bytes),這個(gè)占用最深的靜態(tài)調(diào)用為 “main->LED_GPIO_Config->GPIO_Init”。注意這里給出的空間只是靜態(tài)的棧使用統(tǒng)計(jì),鏈接器無(wú)法統(tǒng)計(jì)動(dòng)態(tài)使用情況,例如鏈接器無(wú)法知道遞歸函數(shù)的遞歸深度。在本文件的后面還可查詢(xún)到其它函數(shù)的調(diào)用情況及其它細(xì)節(jié)。
利用這些信息,我們可以大致了解工程中應(yīng)該分配多少空間給棧,有空間余量的情況下,一般會(huì)設(shè)置比這個(gè)靜態(tài)最深棧使用量大一倍,在 STM32 中可修改啟動(dòng)文件改變堆棧的大小;如果空間不足,可從該文件中了解到調(diào)用深度的信息,然后優(yōu)化該代碼。
注意:
查看了各個(gè)工程的靜態(tài)調(diào)用圖文件統(tǒng)計(jì)后,我們發(fā)現(xiàn)本書(shū)提供的一些比較大規(guī)模的工程例子,靜態(tài)棧調(diào)用最大深度都已超出 STM32 啟動(dòng)文件默認(rèn)的棧空間大小 0x00000400,即 1024 字節(jié),但在當(dāng)時(shí)的調(diào)試過(guò)程中卻沒(méi)有發(fā)現(xiàn)錯(cuò)誤,因此我們也沒(méi)有修改棧的默認(rèn)大小(有一些工程調(diào)試時(shí)已發(fā)現(xiàn)問(wèn)題,它們的棧空間就已經(jīng)被我們改大了),雖然這些工程實(shí)際運(yùn)行并沒(méi)有錯(cuò)誤,但這可能只是因?yàn)樗褂玫臈R绯?RAM 空間恰好沒(méi)被程序其它部分修改而已。所以,建議您在實(shí)際的大型工程應(yīng)用中(特別是使用了各種外部庫(kù)時(shí),如 Lwip/emWin/Fatfs 等),要查看本靜態(tài)調(diào)用圖文件,了解程序的棧使用情況,給程序分配合適的棧空間。(關(guān)于 Listing 目錄下的介紹。請(qǐng)看下文——MDK的編譯過(guò)程及文件類(lèi)型全解——(三))
轉(zhuǎn)載自—第48章 MDK的編譯過(guò)程及文件類(lèi)型全解
總結(jié)
以上是生活随笔為你收集整理的MDK的编译过程及文件类型全解——(二)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: uni-app 二维码转base64 分
- 下一篇: 考研线性代数题型总结