日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

MDK的编译过程及文件类型全解——(二)

發布時間:2023/12/8 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 MDK的编译过程及文件类型全解——(二) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言:

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


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


1. MDK 相關文件

1.1 uvprojx 文件

uvprojx 文件就是我們平時雙擊打開的工程文件,它記錄了整個工程的結構,如芯片類型、工程包含了哪些源文件等內容,見下圖 :

1.2 uvoptx 文件

uvoptx 文件記錄了工程的配置選項,如下載器的類型、變量跟蹤配置、斷點位置以及當前已打開的文件等等,見下圖:

1.3 uvguix 文件

uvguix文件記錄了 MDK 軟件的 GUI 布局,如代碼編輯區窗口的大小、編譯輸出提示窗口的位置等等。

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


這些工程參數都是當 MDK 正常退出時才會被寫入保存,若 MDK 錯誤退出時(如使用 Windows 的任務管理器強制關閉),工程配置參數的最新更改是不會被記錄的,重新打開工程時要再次配置。根據這幾個文件的記錄類型,可以知道 uvprojx 文件是最重要的,刪掉它我們就無法再正常打開工程了,而 uvoptx 及 uvguix 文件并不是必須的,可以刪除,重新使用 MDK 打開 uvprojx 工程文件后,會以默認參數重新創建 uvoptx 及 uvguix 文件。(所以當使用 Git/SVN 等代碼管理的時候,往往只保留 uvproj x文件)

2. 源文件

源文件是工程中我們最熟悉的內容了,它們就是我們編寫的各種源代碼,MDK 支持 c、cpp、h、s、inc 類型的源代碼文件,其中 c、cpp 分別是 c/c++ 語言的源代碼,h 是它們的頭文件,s 是匯編文件,inc 是匯編文件的頭文件,可使用 “$include” 語法包含。編譯器根據工程中的源文件最終生成機器碼。

3 . Output 目錄下生成的文件

點擊 MDK 中的編譯按鈕,它會根據工程的配置及工程中的源文件輸出各種對象和列表文件,在工程的 “Options for Targe->Output->Select Folder for Objects” 和 “Options for Targe->Listing->Select Folder for Listings” 選項配置它們的輸出路徑::Output 輸出路徑、Listing 輸出路徑。

編譯后 Output 和 Listing 目錄下生成的文件見下圖:

3.1 lib 庫文件

在某些場合下我們希望提供給第三方一個可用的代碼庫,但不希望對方看到源碼,這個時候我們就可以把工程生成 lib 文件 (Library file) 提供給對方,在 MDK 中可配置 "Options for Target->Create Library"選 項把工程編譯成庫文件,見下圖:


工程中生成可執行文件或庫文件只能二選一,默認編譯是生成可執行文件的,可執行文件即我們下載到芯片上直接運行的機器碼。

得到生成的 .lib 文件后,可把它像 C 文件一樣添加到其它工程中,并在該工程調用 lib 提供的函數接口,除了不能看到 .lib 文件的源碼,在應用方面它跟 C 源文件沒有區別。

3.2 dep、d 依賴文件

.dep 和 .d 文件 (Dependency file) 記錄的是工程或其它文件的依賴,主要記錄了引用的頭文件路徑,其中 .dep 是整個工程的依賴,它以工程名命名,而 .d 是單個源文件的依賴,它們以對應的源文件名命名。這些記錄使用文本格式存儲,我們可直接使用記事本打開,如下圖:


3.3 crf 交叉引用文件

.crf 是交叉引用文件 (Cross-Reference file),它主要包含了瀏覽信息 (browse information),即源代碼中的宏定義、變量及函數的定義和聲明的位置。

我們在代碼編輯器中點擊 “Go To Definition Of ‘xxxx’” 可實現瀏覽跳轉,見下圖,跳轉的時候,MDK 就是通過 .crf 文件查找出跳轉位置的。

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


.crf 文件使用了特定的格式表示,直接用文本編輯器打開會看到大部分亂碼,見下圖,這里不作深入研究。

3.4 o、axf及elf文件

.o、.elf、.axf、.bin 及 .hex 文件都存儲了編譯器根據源代碼生成的機器碼,根據應用場合的不同,它們又有所區別。

3.4.1 ELF 文件說明

.o、.elf、.axf 以及前面提到的 lib 文件都是屬于目標文件,它們都是使用 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 都屬于這一類。

3.4.1.1 o文件與axf文件的關系

根據上面的分類,我們了解到, .axf 文件是由多個 .o 文件鏈接而成的,而 .o 文件由相應的源文件編譯而成,一個源文件對應一個 .o 文件。它們的關系見下圖:

上圖中的中間代表的是 armlink 鏈接器,在它的右側是輸入鏈接器的 .o 文件,左側是它輸出的 .axf 文件。

可以看到,由于都使用 ELF 文件格式, .o 與 .axf 文件的結構是類似的,它們包含 ELF 文件頭、程序頭、節區 (section) 以及節區頭部表。各個部分的功能說明如下:

(1)ELF 文件頭用來描述整個文件的組織,例如數據的大小端格式,程序頭、節區頭在文件中的位置等。

(2)程序頭告訴系統如何加載程序,例如程序主體存儲在本文件的哪個位置,程序的大小,程序要加載到內存什么地址等等。MDK 的可重定位文件 .o 不包含這部分內容,因為它還不是可執行文件,而 armlink 輸出的 .axf 文件就包含該內容了。

(3)節區是 .o 文件的獨立數據區域,它包含提供給鏈接視圖使用的大量信息,如指令 (Code)、數據 (RO、RW、ZI-data)、符號表(函數、變量名等)、重定位信息等,例如每個由 C 語言定義的函數在 .o 文件中都會有一個獨立的節區;

(4)存儲在最后的節區頭則包含了本文件節區的信息,如節區名稱、大小等等。

總的來說,鏈接器把各個 .o 文件的節區歸類、排列,根據目標器件的情況編排地址生成輸出,匯總到 .axf 文件。例如,見下圖,“多彩流水燈” 工程中在 “bsp_led.c” 文件中有一個 LED_GPIO_Config 函數,而它內部調用了 “stm32f4xx_gpio.c” 的 GPIO_Init 函數,經過 armcc 編譯后,LED_GPIO_Config 及 GPIO_Iint 函數都成了指令代碼,分別存儲在 bsp_led.o 及 stm32f4xx_gpio.o 文件中,這些指令在 .o 文件都沒有指定地址,僅包含了內容、大小以及調用的鏈接信息,而經過鏈接器后,鏈接器給它們都分配了特定的地址,并且把地址根據調用指向鏈接起來。

3.4.1.2 ELF 文件頭

接下來我們看看具體文件的內容,使用 fromelf 文件可以查看 .o、 .axf 及 .lib 文件的 ELF 信息。

使用命令行,切換到文件所在的目錄,輸入 “fromelf –text –v bsp_led.o” 命令,可控制輸出 bsp_led.o 的詳細信息,見下圖。利用 “-c、-z” 等選項還可輸出反匯編指令文件、代碼及數據文件等信息,請親手嘗試一下。

為了便于閱讀,已使用 fromelf 指令生成了 “多彩流水燈.axf”、“bsp_led” 及"多彩流水燈 .lib" 的 ELF 信息,并已把這些信息保存在獨立的文件中,參見下表。

========================================================================** ELF Header InformationFile Name:.\bsp_led.o //bsp_led.o文件Machine class: ELFCLASS32 (32-bit) //32位機Data encoding: ELFDATA2LSB (Little endian) //小端格式Header version: EV_CURRENT (Current version)Operating System ABI: noneABI Version: 0File Type: ET_REL (Relocatable object) (1) //可重定位文件類型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) //程序頭在文件中的位置(沒有程序頭)Section header offset: 507224 (0x0007bd58) //節區頭在文件中的位置Section header string table index: 243=====================================================================

在上述代碼中已加入了部分注釋,解釋了相應項的意義,值得一提的是在這個 .o 文件中,它的 ELF 文件頭中告訴我們它的程序頭 (Program header) 大小為 “0 bytes”,且程序頭所在的文件位置偏移也為 “0”,這說明它是沒有程序頭的。

3.4.1.3 程序頭
===================================================================** ELF Header InformationFile Name:.\多彩流水燈.axf //多彩流水燈.axf 文件Machine class: ELFCLASS32 (32-bit) //32位機Data encoding: ELFDATA2LSB (Little endian) //小端格式Header version: EV_CURRENT (Current version)Operating System ABI: noneABI Version: 0File Type: ET_EXEC (Executable) (2) //可執行文件類型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) //節區頭在文件中的位置Section header string table index: 14=================================================================** Program header #0Type : PT_LOAD (1) //表示這是可加載的內容File Offset : 52 (0x34) //在文件中的偏移Virtual Addr : 0x08000000 //虛擬地址(此處等于物理地址)Physical Addr : 0x08000000 //物理地址Size in file : 1456 bytes (0x5b0) //程序在文件中占據的大小Size in memory: 2480 bytes (0x9b0) //若程序加載到內存,占據的內存空間Flags : PF_X + PF_W + PF_R + PF_ARM_ENTRY (0x80000007)Alignment : 8 //地址對齊===============================================================

對比之下,可發現 .axf 文件的 ELF 文件頭對程序頭的大小說明為非 0 值,且給出了它在文件的偏移地址,在輸出信息之中,包含了程序頭的詳細信息??煽吹?#xff0c;程序頭的 “Physical Addr” 描述了本程序要加載到的內存地址 “0x0800 0000”,正好是 STM3 2內部 FLASH 的首地址;“size in file” 描述了本程序占據的空間大小為 “1456 bytes”,它正是程序燒錄到 FLASH 中需要占據的空間。

3.4.1.4 節區頭

在 ELF 的原文件中,緊接著程序頭的一般是節區的主體信息,在節區主體信息之后是描述節區主體信息的節區頭.

====================================// Section #4Name : i.LED_GPIO_Config //節區名//此節區包含程序定義的信息,其格式和含義都由程序來解釋。Type : SHT_PROGBITS (0x00000001)//此節區在進程執行過程中占用內存。節區包含可執行的機器指令。Flags :SHF_ALLOC + SHF_EXECINSTR (0x00000006)Addr : 0x00000000 //地址File Offset : 68 (0x44) //在文件中的偏移Size : 116 bytes (0x74) //大小Link : SHN_UNDEFInfo : 0Alignment : 4 //字節對齊Entry Size : 0====================================

這個節區的名稱為 LED_GPIO_Config,它正好是我們在 bsp_led.c 文件中定義的函數名,這個節區頭描述的是該函數被編譯后的節區信息,其中包含了節區的類型(指令類型)、節區應存儲到的地址 (0x00000000)、它主體信息在文件位置中的偏移 (68) 以及節區的大小 (116 bytes)。

由于 .o 文件是可重定位文件,所以它的地址并沒有被分配,是 0x00000000(假如文件中還有其它函數,該函數生成的節區中,對應的地址描述也都是 0)。當鏈接器鏈接時,根據這個節區頭信息,在文件中找到它的主體內容,并根據它的類型,把它加入到主程序中,并分配實際地址,鏈接后生成的 .axf 文件,我們再來看看它的內容:

========================================================================** Section #1Name : ER_IROM1 //節區名//此節區包含程序定義的信息,其格式和含義都由程序來解釋。Type : SHT_PROGBITS (0x00000001)//此節區在進程執行過程中占用內存。節區包含可執行的機器指令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 //節區名//包含將出現在程序的內存映像中的為初始//化數據。根據定義,當程序開始執行,系統//將把這些數據初始化為 0。Type : SHT_NOBITS (0x00000008)//此節區在進程執行過程中占用內存。節區包含進程執行過程中將可寫的數據。Flags : SHF_ALLOC + SHF_WRITE (0x00000003)Addr : 0x20000000 //地址File Offset : 1508 (0x5e4)Size : 1024 bytes (0x400) //大小Link : SHN_UNDEFInfo : 0Alignment : 8Entry Size : 0====================================

在 .axf 文件中,主要包含了兩個節區,一個名為 ER_IROM1,一個名為 RW_IRAM1,這些節區頭信息中除了具有 .o 文件中節區頭描述的節區類型、文件位置偏移、大小之外,更重要的是它們都有具體的地址描述,其中 ER_IROM1 的地址為 0x08000000,而 RW_IRAM1 的地址為 0x20000000,它們正好是內部 FLASH 及 SRAM 的首地址,對應節區的大小就是程序需要占用 FLASH 及 SRAM 空間的實際大小。

也就是說,經過鏈接器后,它生成的 .axf 文件已經匯總了其它 .o 文件的所有內容,生成的 ER_IROM1 節區內容可直接寫入到 STM32 內部 FLASH 的具體位置。例如,前面 .o 文件中的 i.LED_GPIO_Config 節區已經被加入到 .axf 文件的ER_IROM1 節區的某地址。

3.4.1.5 節區主體及反匯編代碼

使用 fromelf 的 -c 選項可以查看部分節區的主體信息,對于指令節區,可根據其內容查看相應的反匯編代碼。

//.o文件的LED_GPIO_Config節區及反匯編代碼** 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// 地址內容 (ASCII碼) 內容對應的代碼// (無意義)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 文件,它的節區地址還是沒有分配的,基地址為 0x00000000,接著在 LED_GPIO_Config 標號之后,列出了一個表,表中包含了地址偏移、相應地址中的內容以及根據內容反匯編得到的指令。細看匯編指令,還可看到它包含了跳轉到 RCC_AHB1PeriphClockCmd 及 GPIO_Init 標號的語句,而且這兩個跳轉語句原來的內容都是 “f7fffffe”,這是因為還 .o 文件中并沒有 RCC_AHB1PeriphClockCmd 及 GPIO_Init 標號的具體地址索引,在 .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/*....以下省略**/

可看到,除了基地址以及跳轉地址不同之外,LED_GPIO_Config 中的內容跟 .o 文件中的一樣。另外,由于 .o 是獨立的文件,而 .axf 是整個工程匯總的文件,所以在 .axf 中包含了所有調用到 .o 文件節區的內容。

在 .axf 文件中,跳轉到 RCC_AHB1PeriphClockCmd 及 GPIO_Init 標號的這兩個指令后都有注釋,分別是 “; 0x8000320” 及 “; 0x800021e”,它們是這兩個標號所在的具體地址,而且這兩個跳轉語句的跟 .o 中的也有區別,內容分別為 “f000f838e” 及 “f7ffffa5”( .o中的均為f7fffffe)。這就是鏈接器鏈接的含義,它把不同 .o 中的內容鏈接起來了。

3.4.1.6 分散加載代碼

學習至此,還有一個疑問,前面提到程序有存儲態及運行態,它們之間應有一個轉化過程,把存儲在 FLASH 中的 RW-data 數據拷貝至 SRAM。然而我們的工程中并沒有編寫這樣的代碼,在匯編文件中也查不到該過程,芯片是如何知道 FLASH 的哪些數據應拷貝到 SRAM 的哪些區域呢?程序中具有一段名為 “__scatterload” 的分散加載代碼,它是由 armlink 鏈接器自動生成的.

//分散加載代碼.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

這段分散加載代碼包含了拷貝過程( LDM 復制指令),而 LDM 指令的操作數中包含了加載的源地址,這些地址中包含了內部 FLASH 存儲的 RW-data 數據。而 "__scatterload " 的代碼會被 “__main” 函數調用,__main 在啟動文件中的 “Reset_Handler” 會被調用,因而,在主體程序執行前,已經完成了分散加載過程。

_main_main_stk0x080001ac: f8dfd00c .... LDR sp,__lit__00000000 ; [0x80001bc] = 0x20000400.ARM.Collect$$$$00000004_main_scatterload0x080001b0: f000f818 .... BL __scatterload ; 0x80001e4

3.5 hex文件及bin文件

若編譯過程無誤,即可把工程生成前面對應的 .axf 文件,而在 MDK 中使用下載器 (DAP/JLINK/ULINK 等)下載程序或仿真的時候,MDK 調用的就是 .axf 文件,它解釋該文件,然后控制下載器把 .axf 中的代碼內容下載到 STM32 芯片對應的存儲空間,然后復位后芯片就開始執行代碼了。

然而,脫離了 MDK 或 IAR 等工具,下載器就無法直接使用 .axf 文件下載代碼了,它們一般僅支持 hex 和 bin 格式的代碼數據文件。默認情況下 MDK 都不會生成 hex 及 bin 文件,需要配置工程選項或使用 fromelf 命令。

3.5.1 生成hex文件

生成 hex 文件的配置比較簡單,在 “Options for Target->Output->Create Hex File” 中勾選該選項,然后編譯工程即可,見下圖 :

3.5.2 生成bin文件

使用 MDK 生成 bin 文件需要使用 fromelf 命令,在 MDK 的 “Options For Target->Users” 中加入下圖的命令。

圖中的指令內容為:

“fromelf --bin --output …\Output\多彩流水燈.bin …\Output\多彩流水燈.axf”

該指令是根據本機及工程的配置而寫的,在不同的系統環境或不同的工程中,指令內容都不一樣,我們需要理解它,才能為自己的工程定制指令,首先看看 fromelf 的幫助,見下圖:

我們在 MDK 輸入的指令格式是遵守 fromelf 幫助里的指令格式說明的,其格式為:

“fromelf [options] input_file”

其中 optinos 是指令選項,一個指令支持輸入多個選項,每個選項之間使用空格隔開,我們的實例中使用 “–bin” 選項設置輸出 bin 文件,使用 “–output file” 選項設置輸出文件的名字為 “…\Output\多彩流水燈.bin”,這個名字是一個相對路徑格式,如果不了解如何使用 “…” 表示路徑,可使用 MDK 命令輸入框后面的文件夾圖標打開文件瀏覽器選擇文件,在命令的最后使用 “…\Output\多彩流水燈.axf” 作為命令的輸入文件。具體的格式分解見下圖:


fromelf 需要根據工程的 .axf 文件輸入來轉換得到 bin 文件,所以在命令的輸入文件參數中要選擇本工程對應的 .axf 文件,在 MDK 命令輸入欄中,我們把 fromelf 指令放置在 “After Build/Rebuild” (工程構建完成后執行)一欄也是基于這個考慮,這樣設置后,工程構建完成生成了最新的 .axf 文件,MDK 再執行 fromelf 指令,從而得到最新的 bin 文件。

設置完成生成 hex 的選項或添加了生成 bin 的用戶指令后,點擊工程的編譯 (build)按鈕,重新編譯工程,成功后可看到下圖中的輸出。打開相應的目錄即可找到文件,若找不到 bin 文件,請查看提示輸出欄執行指令的信息,根據信息改正 fromelf 指令。

其中 bin 文件是純二進制數據,無特殊格式,接下來我們了解一下 hex 文件格式。

3.5.3 hex文件格式

hex 是 Intel 公司制定的一種使用 ASCII 文本記錄機器碼或常量數據的文件格式,這種文件常常用來記錄將要存儲到 ROM 中的數據,絕大多數下載器支持該格式。

一個 hex 文件由多條記錄組成,而每條記錄由五個部分組成,格式形如 “: ll aaaatt[dd…]cc”,例如本"多彩流水燈"工程生成的 hex 文件前幾條記錄如下:

:020000040800F2:1000000000040020C10100081B030008A30200082F:100010001903000809020008690400080000000034:100020000000000000000000000000003D03000888:100030000B020008000000001D0300081504000862:10004000DB010008DB010008DB010008DB01000820

記錄的各個部分介紹如下:

(1) “:” :每條記錄的開頭都使用冒號來表示一條記錄的開始;

(2) ll :以 16 進制數表示這條記錄的主體數據區的長度(即后面[dd…]的長度);

(3) aaaa:表示這條記錄中的內容應存放到 FLASH 中的起始地址;

(4) tt:表示這條記錄的類型,它包含中的各種類型;

(5) dd:表示一個字節的數據,一條記錄中可以有多個字節數據,ll 區表示了它有多少個字節的數據;

(6) cc:表示本條記錄的校驗和,它是前面所有 16 進制數據 (除冒號外,兩個為一組)的和對 256 取模運算的結果的補碼。

例如,上面的第一條記錄解釋如下:

(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:表示這條記錄數據區的長度為 16 字節;

(2) 0000:表示這條記錄所在的地址,與前面的擴展記錄結合,表示這條記錄要存儲的 FLASH 首地址為(0x0800 0000+0x0000);

(3) 00:表示這是一條數據記錄,數據區的是地址;

(4) 00040020C10100081B030008A3020008:這是要按地址存儲的數據;

(5) 2F:校驗和

為了更清楚地對比 bin、hex 及 axf 文件的差異,我們來查看這些文件內部記錄的信息來進行對比。

3.5.4 hex、bin及axf文件的區別與聯系

bin、hex 及 axf 文件都包含了指令代碼,但它們的信息豐富程度是不一樣的。

(1) bin 文件是最直接的代碼映像,它記錄的內容就是要存儲到 FLASH 的二進制數據(機器碼本質上就是二進制數據),在 FLASH 中是什么形式它就是什么形式,沒有任何輔助信息,包括大小端格式也沒有,因此下載器需要有針對芯片 FLASH 平臺的輔助文件才能正常下載(一般下載器程序會有匹配的這些信息);

(2) hex 文件是一種使用十六進制符號表示的代碼記錄,記錄了代碼應該存儲到 FLASH 的哪個地址,下載器可以根據這些信息輔助下載;

(3) axf 文件在前文已經解釋,它不僅包含代碼數據,還包含了工程的各種信息,因此它也是三個文件中最大的。

同一個工程生成的 bin、hex 及 axf 文件的大小見下圖:

實際上,這個工程要燒寫到 FLASH 的內容總大小為 1456 字節,然而在 Windows 中查看的 bin 文件卻比它大( bin 文件是 FLASH 的代碼映像,大小應一致),這是因為 Windows 文件顯示單位的原因,使用右鍵查看文件的屬性,可以查看它實際記錄內容的大小,見下圖:

接下來我們打開本工程的 “多彩流水燈.bin”、“多彩流水燈 .hex “及由"多彩流水燈.axf” 使用 fromelf 工具輸出的反匯編文件"多彩流水燈 _axf_elfInfo_c.txt” 文件,清晰地對比它們的差異,見下圖。如果您想要親自閱讀自己電腦上的 bin 文件,推薦使用 sublime 軟件打開,它可以把二進制數以 ASCII 碼呈現出來,便于閱讀。


在 hex 文件中包含了地址信息以及地址中的內容,而在 bin 文件中僅包含了內容,連存儲的地址信息都沒有。觀察可知,bin、hex及axf 文件中的數據內容都是相同的,它們存儲的都是機器碼。這就是它們三都之間的區別與聯系。

由于文件中存儲的都是機器碼,見下圖,該圖是根據 axf 文件的 GPIO_Init 函數的機器碼,在 bin 及 hex 中找到的對應位置。所以經驗豐富的人是有可能從 bin 或 hex 文件中恢復出匯編代碼的,只是成本較高,但不是不可能。

如果芯片沒有做任何加密措施,使用下載器可以直接從芯片讀回它存儲在 FLASH 中的數據,從而得到 bin 映像文件,根據芯片型號還原出部分代碼即可進行修改,甚至不用修改代碼,直接根據目標產品的硬件 PCB,抄出一樣的板子,再把 bin 映像下載芯片,直接山寨出目標產品,所以在實際的生產中,一定要注意做好加密措施。由于 axf 文件中含有大量的信息,且直接使用 fromelf 即可反匯編代碼,所以更不要隨便泄露 axf 文件。lib 文件也能反使用 fromelf 文件反匯編代碼,不過它不能還原出 C 代碼,由于 lib 文件的主要目的是為了保護 C 源代碼,也算是達到了它的要求。

3.5.5 htm靜態調用圖文件

Output 目錄下,有一個以工程文件命名的后綴為 .bulid_log.htm 及 .htm 文件,如"多彩流水燈 .bulid_log.htm" 及"多彩流水燈 .htm",它們都可以使用瀏覽器打開。其中 .build_log.htm 是工程的構建過程日志,而 .htm 是鏈接器生成的靜態調用圖文件。

在靜態調用圖文件中包含了整個工程各種函數之間互相調用的關系圖,而且它還給出了靜態占用最深的??臻g數量以及它對應的調用關系鏈。

該文件說明了本工程的靜態??臻g最大占用 56 字節 (Maximum Stack Usage:56bytes),這個占用最深的靜態調用為 “main->LED_GPIO_Config->GPIO_Init”。注意這里給出的空間只是靜態的棧使用統計,鏈接器無法統計動態使用情況,例如鏈接器無法知道遞歸函數的遞歸深度。在本文件的后面還可查詢到其它函數的調用情況及其它細節。

利用這些信息,我們可以大致了解工程中應該分配多少空間給棧,有空間余量的情況下,一般會設置比這個靜態最深棧使用量大一倍,在 STM32 中可修改啟動文件改變堆棧的大小;如果空間不足,可從該文件中了解到調用深度的信息,然后優化該代碼。

注意:

查看了各個工程的靜態調用圖文件統計后,我們發現本書提供的一些比較大規模的工程例子,靜態棧調用最大深度都已超出 STM32 啟動文件默認的??臻g大小 0x00000400,即 1024 字節,但在當時的調試過程中卻沒有發現錯誤,因此我們也沒有修改棧的默認大小(有一些工程調試時已發現問題,它們的棧空間就已經被我們改大了),雖然這些工程實際運行并沒有錯誤,但這可能只是因為它使用的棧溢出 RAM 空間恰好沒被程序其它部分修改而已。所以,建議您在實際的大型工程應用中(特別是使用了各種外部庫時,如 Lwip/emWin/Fatfs 等),要查看本靜態調用圖文件,了解程序的棧使用情況,給程序分配合適的??臻g。(關于 Listing 目錄下的介紹。請看下文——MDK的編譯過程及文件類型全解——(三))


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

總結

以上是生活随笔為你收集整理的MDK的编译过程及文件类型全解——(二)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。