ARM Cortex-M底層技術(3)—編譯內核的原理及其應用


概述:

  當前開發中,我使用的Keil開發工具較多(keil526),故以keil為例進行介紹,其他開發環境大同小異。

1. 編譯鏈接的定義

 不管我們編寫的代碼有多么簡單,都必須經過「編譯 --> 鏈接」的過程才能生成可執行文件:

  • 編譯就是將我們編寫的源代碼“翻譯”成計算機可以識別的二進制格式,它們以目標文件的形式存在;
  • 鏈接就是一個“打包”的過程,它將所有的目標文件以及系統組件組合成一個可執行文件。

 拋開嵌入式而言,C語言的編譯器有很多種,不同的平台下有不同的編譯器,例如:

  • Windows 下常用的是微軟開發的 Visual C++,它被集成在 Visual Studio 中,一般不單獨使用;
  • Linux 下常用的是 GUN 組織開發的GCC,很多 Linux 發行版都自帶 GCC;
  • Mac 下常用的是 LLVM/Clang,它被集成在 Xcode 中(Xcode 以前集成的是 GCC,后來由於 GCC 的不配合才改為 LLVM/Clang,LLVM/Clang 的性能比 GCC 更加強大)。

  意思就是說,在windows平台下進行開發時,我們選用的開發工具大部分是幫我們集成了一些編譯器,只需要進行界面配置就可以了。這里我要最好是知道Keil開發環境中,這些編譯鏈接工具是怎么使用的。

keil中編譯鏈接如下,下面會對編譯連接器進行解釋:

2. Keil的編譯鏈接

  在第一講中提過,當我們利用Keil進行編譯一個工程時,下面的輸出框中的內容是這樣的:

Compiler編譯器,可以看到該編譯器在我們電腦中的D盤D:\Keil_v526\ARM\ARMCC\bin,如下圖所示:

  armar 是用於把.o 文件打包成 lib 文件,armasm 編譯匯編文件 ,armcc 編譯 c/c++文件 ,armlink 鏈接對象文件 ,fromelf 生成下載格式文件,它根據 axf 映像文件轉化成 hex 文件,並列出編譯過程出現的錯誤(Error)和警告(Warning)數量 。調用這些編譯工具,需要用到Windows的“命令行提示符工具”,為了讓命令行方便地找到這些工具,我們先把工具鏈的目錄添加到系統的環境變量中。

2.1 添加環境變量

  Win7 系統為例添加工具鏈的路徑到 PATH 環境變量 。

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

 

 

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

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

 

 

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

  這個過程本質就是讓命令行通過“PATH”路徑找到“fromelf.exe”程序運行,默認運行“fromelf.exe”時它會輸出自己的幫助信息,這就是工具鏈的調用過程, Keil本質上也是如此調用工具鏈的,只是它集成為 GUI(界面),相對於命令行對用戶更友好,畢竟上述配置環境變量的過程已經讓新手煩躁了。解釋一下,這個cmd框中的內容怎么和Keil對應起來。fromelf 可根據 axf 文件生成 hexbin 文件, hex和 bin 文件是大多數下載器支持的下載文件格式 。例如如果我們想利用 fromelf 生成 bin 文件,可以在 MDK的“Option for Target->User”頁中添加調用 fromelf 的指令,如下圖

 

 

還有鏈接器的配置界面如下:

 

 

 

 那么這些東西有什么用呢?來看一下這段代碼:

 1 Reset_Handler   PROC
 2                 EXPORT  Reset_Handler               [WEAK]
 3                 IMPORT  SystemInit
 4                 IMPORT  __main
 5  
 6                 LDR     r0, =errorfunc
 7                 BLX     r0
 8                 LDR     r0, =__main
 9                 BX      r0
10                 ENDP

  啟動代碼中的Reset_Handler代碼,大家可以一眼看出來這段代碼是錯誤的,其中紅色字體的errorfunc是我故意填在這里的一個不存在的函數,編譯必然報錯,那么我們看看編譯器是怎么報錯的?

 重點在途中藍色底紋的部分,注意第一個單詞:assembling……,然后后面跟着出錯信息,沒錯,這是一個匯編器錯誤;再看下面,我再代碼里面給出一個錯誤(我在main.c里面刪掉了一個頭文件):

看藍色底紋部分,注意第一個單詞:compiling……,然后后面跟着出錯信息,沒錯,這是一個編譯器錯誤,跟上面的不同,這是編譯器報錯,我們再看下面一個錯誤,代碼如下

 1 int main(void)
 2 {
 3     char ch;
 4  
 5     /* Init board hardware. */
 6     /* attach 12 MHz clock to FLEXCOMM0 (debug console) */
 7     CLOCK_AttachClk(BOARD_DEBUG_UART_CLK_ATTACH);
 8  
 9     BOARD_InitPins();
10     BOARD_BootClockFROHF48M();
11     BOARD_InitDebugConsole();
12  
13     PRINTF("hello world.\r\n");
14  
15     while (1)
16     {
17         ch = errorfunc();
18         PUTCHAR(ch);
19     }
20 }

看藍色底紋部分,注意第一個單詞:linking……,然后后面跟着出錯信息,這次是一個鏈接器錯誤!

  大家經常說的編譯器報錯,其實是幾個不同的東西再報錯,編譯器、匯編器、鏈接器都會報錯,那你可能會問,知道這個有啥用呢?當然是有用的,比如匯編器報錯,基本跟C語言沒關系,基本可以斷定是匯編語言語法錯誤或者是嵌入C語言的匯編語言出錯(在C中嵌入匯編是一種非常有效的編程手段);如果是鏈接器報錯,那就基本跟C語言語法無關,不是你的C語法上出錯,很可能是你調用了不存在的函數或者鏈接器腳本寫錯了,或者使用了不存在的標號Symbol,或者沒有包含頭文件.h;而只有編譯器報錯,才總是你的C寫的有問題.
 
3. 工作報表.map文件

   真正有用的鏈接器描述文件“*.map”非常有用(編譯鏈接后,雙擊工程列表下的第二個文件夾可以打開,變成灰色的那個)

  “*.map”絕對是你的“核心員工”的工作報表,也是最復雜的一個。它主要包含交叉鏈接信息,查看該文件可以了解工程中各種符號之間的引用以及整個工程的 Code、 RO-data、 RW-data 以及 ZI-data 的詳細及匯總信息。它的內容中主要包含了“節區的跨文件引用”、“刪除無用節區”、“符號映像表”、“存儲器映像索引”以及“映像組件大小”。

3.1 節區的跨文件引用

 1 Section Cross References
 2 
 3     startup_stm32f10x_hd.o(RESET) refers to startup_stm32f10x_hd.o(STACK) for __initial_sp
 4     startup_stm32f10x_hd.o(RESET) refers to startup_stm32f10x_hd.o(.text) for Reset_Handler
 5     startup_stm32f10x_hd.o(RESET) refers to stm32f10x_it.o(i.NMI_Handler) for NMI_Handler
 6     startup_stm32f10x_hd.o(RESET) refers to stm32f10x_it.o(i.HardFault_Handler) for HardFault_Handler
 7     startup_stm32f10x_hd.o(RESET) refers to stm32f10x_it.o(i.MemManage_Handler) for MemManage_Handler
 8     startup_stm32f10x_hd.o(RESET) refers to stm32f10x_it.o(i.BusFault_Handler) for BusFault_Handler
 9     startup_stm32f10x_hd.o(RESET) refers to stm32f10x_it.o(i.UsageFault_Handler) for UsageFault_Handler
10     startup_stm32f10x_hd.o(RESET) refers to stm32f10x_it.o(i.SVC_Handler) for SVC_Handler
11     startup_stm32f10x_hd.o(RESET) refers to stm32f10x_it.o(i.DebugMon_Handler) for DebugMon_Handler
12     startup_stm32f10x_hd.o(RESET) refers to stm32f10x_it.o(i.PendSV_Handler) for PendSV_Handler

......(省略)

  在這部分中,詳細列出了各個*.o 文件之間的符號引用。由於*.o 文件是由 asm 或 c/c++源文件編譯后生成的,各個文件及文件內的節區間互相獨立,鏈接器根據它們之間的互相引用鏈接起來,鏈接的詳細信息在這個“Section Cross References”一一列出。

  解釋一下第3行,其他行的解釋也差不多,第3行說明的是 startup_stm32f10x.o 文件中的“RESET”節區中的“__initial_sp” 符號(/函數)引用了同文件“STACK”節區(/函數)。這些跨文件引用的符號其實就是源文件中的函數名、變量名 。

3.2 刪除無用節區

 1 Removing Unused input sections from the image.
 2 
 3     Removing startup_stm32f10x_hd.o(HEAP), (512 bytes).
 4     Removing core_cm3.o(.emb_text), (32 bytes).
 5     Removing system_stm32f10x.o(i.SystemCoreClockUpdate), (164 bytes).
 6     Removing system_stm32f10x.o(.data), (20 bytes).
 7     Removing misc.o(i.NVIC_Init), (112 bytes).
 8     Removing misc.o(i.NVIC_PriorityGroupConfig), (20 bytes).
 9     Removing misc.o(i.NVIC_SetVectorTable), (20 bytes).
10     Removing misc.o(i.NVIC_SystemLPConfig), (32 bytes).
11     Removing misc.o(i.SysTick_CLKSourceConfig), (40 bytes).
12     Removing stm32f10x_adc.o(i.ADC_AnalogWatchdogCmd), (20 bytes).
13     Removing stm32f10x_adc.o(i.ADC_AnalogWatchdogSingleChannelConfig), (16 bytes).
14     Removing stm32f10x_adc.o(i.ADC_AnalogWatchdogThresholdsConfig), (6 bytes).
15     Removing stm32f10x_adc.o(i.ADC_AutoInjectedConvCmd), (22 bytes).

......(省略)

  這部分列出了在鏈接過程它發現工程中未被引用的節區,這些未被引用的節區將會被刪除(指不加入到*.axf 文件,不是指在*.o 文件刪除),這樣可以防止這些無用數據占用程序空間。這部分是編譯器自動做的,不需要人工參與。

3.3 符號映像表

 

   這個表列出了被引用的各個符號在存儲器中的具體地址、占據的空間大小等信息。如我們可以查到 LED_GPIO_Config 符號(0x080002c5)存儲在 0x080002c4 地址,它屬於 Thumb Code 類型,大小為 90 字節,它所在的節區為 bsp_led.o 文件的 i.LED_GPIO_Config 節區。

3.4  存儲器映像索引

 1 Memory Map of the image
 2 
 3   Image Entry point : 0x08000131
 4 
 5   Load Region LR_IROM1 (Base: 0x08000000, Size: 0x000005d4, Max: 0x00080000, ABSOLUTE)
 6 
 7     Execution Region ER_IROM1 (Exec base: 0x08000000, Load base: 0x08000000, Size: 0x000005d4, Max: 0x00080000, ABSOLUTE)
 8 
 9     Exec Addr    Load Addr    Size         Type   Attr      Idx    E Section Name        Object
10 
11     0x08000000   0x08000000   0x00000130   Data   RO            3    RESET               startup_stm32f10x_hd.o
12     0x08000130   0x08000130   0x00000000   Code   RO         3207  * .ARM.Collect$$$$00000000  mc_w.l(entry.o)
13     0x08000130   0x08000130   0x00000004   Code   RO         3210    .ARM.Collect$$$$00000001  mc_w.l(entry2.o)
14     0x08000134   0x08000134   0x00000004   Code   RO         3213    .ARM.Collect$$$$00000004  mc_w.l(entry5.o)
15     
16     ......
17     
18     0x080002c4   0x080002c4   0x00000060   Code   RO         3192    i.LED_GPIO_Config   bsp_led.o
19     0x08000324   0x08000324   0x00000004   Code   RO         3132    i.MemManage_Handler  stm32f10x_it.o
20     0x08000328   0x08000328   0x00000002   Code   RO         3133    i.NMI_Handler       stm32f10x_it.o
21     0x0800032a   0x0800032a   0x00000002   Code   RO         3134    i.PendSV_Handler    stm32f10x_it.o
22     
23     ......
24 
25 
26     Execution Region RW_IRAM1 (Exec base: 0x20000000, Load base: 0x080005d4, Size: 0x00000400, Max: 0x00010000, ABSOLUTE)
27 
28     Exec Addr    Load Addr    Size         Type   Attr      Idx    E Section Name        Object
29 
30     0x20000000        -       0x00000400   Zero   RW            1    STACK               startup_stm32f10x_hd.o

  本工程的存儲器映像索引分為 ER_IROM1 及 RW_IRAM1 部分,它們分別對應 STM32內部 FLASH 及 SRAM 的空間。相對於符號映像表,這個索引表描述的單位是節區,而且它描述的主要信息中包含了節區的類型及屬性,由此可以區分 Code、 RO-data、 RW-data 及ZI-data。

  從上面的表中我們可以看到 i.LED_GPIO_Config 節區存儲在內部 FLASH 的0x080002c4 地址,大小為 0x00000060,類型為 Code,屬性為 RO。而程序的 STACK 節區(棧空間)存儲在 SRAM 的 0x20000000 地址,大小為 0x00000400,類型為 Zero,屬性為 RW(即 RW-data)。

3.5 映像組件大小 

  map 文件的最后一部分是包含映像組件大小的信息(Image component sizes),這也是最常查詢的內容。

  最后一部分列出了只讀數據(RO)、可讀寫數據(RW)及占據的 ROM 大小。其中只讀數據大小為 1492 字節,它包含 Code 段及 RO-data 段; 可讀寫數據大小為 1024 字節,它包含RW-data 及 ZI-data 段;占據的 ROM 大小為 1492 字節,它除了 Code 段和 RO-data 段,還包含了運行時需要從 ROM加載到 RAM的 RW-data數據(本工程中 RW-data數據為 0字節)。其實,在map文件中,連每一個函數的存儲空間占用情況都會列出來(符號映像表 )。

總結:綜合整個 map 文件的信息,可以分析出,當程序下載到 STM32 的內部 FLASH 時,需要使用的內部 FLASH 是從 0x0800 0000 地址開始的大小為 1492 字節的空間;當程序運行時,需要使用的內部 SRAM 是從 0x20000000 地址開始的大小為 1024 字節的空間。

  如果你真的把這個文件看明白了並能熟練應用,那么恭喜你,你就解鎖了一個重要技能包。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM