程序編譯的幾個階段
一般而言,程序編譯經歷下圖四個階段,鏈接是編譯的最后一步,無論是在PC上編譯代碼,還是在PC上使用嵌入式gcc工具交叉編譯嵌入式代碼,編譯過程都是如下幾步。深入理解鏈接過程是嵌入式工程師必要掌握的能力!
ld鏈接腳本的基礎概念
鏈接過程是將各式各樣的.o文件鏈接為一個文件的過程。鏈接腳本描述連接器如何將這些輸入文件(.o)文件映射為一個輸出文件的,并且定義了輸出文件的memory layout。幾乎所有的鏈接腳本都是在做這些事情。
下面給出一個簡單的鏈接腳本實例,每行腳本都有相應的注解:
左右滑動查看完整內容
SECTIONS { . = 0x10000; .text : { *(.text) } . = 0x8000000; .bss : { *(.bss) } }
上面提到的定位計數器就是點 ‘.’
這個鏈接腳本文件(Linker Scripty),用于告訴鏈接器如何將不同的代碼和數據段(sections)組合在一起形成可執行文件。下面我會解釋其中的每一部分:
1. = 0x10000;
這行代碼重新設置了定位計數器(location counter)的值為0x10000,即地址0x10000。
它告訴鏈接器在此處開始分配.text段的地址空間。
2.text : { *(.text) }
這行代碼定義了一個.text段,并告訴鏈接器將所有名為.text的數據節(section)放入這個段中。
*(.text)表示將所有輸入文件中的.text段合并到輸出文件的.text段中。
3 . = 0x8000000;
這行代碼重新設置了定位計數器的值為0x8000000,即地址0x8000000。
它告訴鏈接器在此處開始分配.data和.bss段的地址空間。
4 .data : { *(.data) }
這行代碼定義了一個.data段,并告訴鏈接器將所有名為.data的數據節放入這個段中。
*(.data)表示將所有輸入文件中的.data段合并到輸出文件的.data段中。
5.bss : { *(.bss) }
這行代碼定義了一個.bss段,并告訴鏈接器將所有名為.bss的數據節放入這個段中。
*(.bss)表示將所有輸入文件中的.bss段合并到輸出文件的.bss段中。
總體來說,這段鏈接腳本告訴鏈接器在特定的地址處分配.text、.data和.bss段,并將對應的數據節合并到這些段中。
鏈接腳本相關的概念
內存(Memory)
左右滑動查看完整內容
MEMORY { name [(attr)] : ORIGIN = origin, LENGTH = len … }
注解:這里的“attr”只能由以下特性組成:
‘R’ Read-only section
‘W’ -- Read/write section
‘X’ -- Executable section
‘A’ -- Allocatable section
‘I’ -- Initialized section
‘L’ -- Same as ‘I’
‘!’ -- Invert the sense of any of the attributes that follow
左右滑動查看完整內容
/* Memories definition */ MEMORY { RAM (xrw) : ORIGIN = 0x20000300, LENGTH = 36K FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K }
注解:
“xrw”表示“RAM”區是可讀、可寫和可執行的,且RAM 的起始地址為“0x20000300”,長度為36K。
“rx”表示“FLASH”區是可讀和可執行的,FLASH的起始地址為“0x08000000”,長度為128K。
段(Section)
Section有loadable(可加載)和allocatable(可分配)兩種類型。不可加載也不可分配的內存段,通常包含某些調試信息。
loadable(可加載)是指:程序運行時,該段內容應該被加載到內存中。
allocatable(可分配)是指:該段的內容應該被預留出,但不應該加載任何別的內容(某些情況下,這些內存必須歸零)。
“可加載”和“可分配”的section都有兩個地址:“VMA”和“LMA”。
VMA(the virtual memory address):這是運行輸出文件時,該section的地址。VMA是可選項,可以不設置。
LMA(load memory address):這是加載section時的地址。
在大多數情況下,這兩個地址是相同的。當然也可以不相等,比如下面的例子就是LMA和VMA不同的案例:
數據段被加載到ROM中,然后在程序啟動時復制到RAM中(通常用于初始化全局變量)。此時ROM地址就是LMA,RAM地址就是VMA。
語法:
左右滑動查看完整內容
SECTIONS { section [address] [(type)] : { [AT(lma)] [ALIGN(section_align) | ALIGN_WITH_INPUT] [SUBALIGN(subsection_align)] [constraint] { output-section-command output-section-command … } [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp] [,] ... }
大多數的段僅使用了上述的一部分屬性。
示例:
左右滑動查看完整內容
/* Sections */ SECTIONS { /* The startup code into "FLASH" Rom type memory */ .isr_vector : { . = ALIGN(4); KEEP(*(.isr_vector)) /* Startup code */ . = ALIGN(4); } >FLASH /* Initialized data sections into "RAM" Ram type memory */ .data: { . = ALIGN(4); _sdata = .; /* create a global symbol at data start */ *(.data) /* .data sections */ *(.data*) /* .data* sections */ . = ALIGN(4); _edata = .; /* define a global symbol at data end */ } >RAM AT> FLASH }
上述示例中“.isr_vector”的LMA與VMA是相等的。“.data”因為有“>RAM AT> FLASH”的修飾,表示.data段的VMA為RAM,LMA為FLASH。即.data段的內容會放在FLASH中,但是運行時,會加載到RAM中。
常用命令
ASSERT
語法:ASSERT(exp, message)
確保exp是非零值,如果為零,將以錯誤碼的形式退出鏈接文件,并輸出message。在必要的位置添加斷言,可以清晰的定位問題。
左右滑動查看完整內容
/* The usage of ASSERT */ .test : { ASSERT ((_estack > (_Min_Stack_Size + _Min_Heap_Size)),"Error: There is an ERR occurred"); }
當示例中的“_estack”大于“_Min_Stack_Size + _Min_Heap_Size”時,就會打印“There is an ERR occurred”。
KEEP
用途:當鏈接器使用('--gc-sections')進行垃圾回收時,KEEP()可以使得被標記段的內容不被清除。
左右滑動查看完整內容
/* The startup code into "FLASH" Rom type memory */ .isr_vector : { . = ALIGN(4); KEEP(*(.isr_vector)) /* Startup code */ . = ALIGN(4); } >FLASH
指定“變量”的輸出地址:
可以定義如下的memory,然后將“變量”存放于該memory,就能控制“變量”的輸出地址。
左右滑動查看完整內容
/* Memories definition */ MEMORY { FW_RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x300 /* 0x20000000 ~ 0x200002FF */ RAM (xrw) : ORIGIN = 0x20000300, LENGTH = 35K FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K }
同時在c文件中,在定義“變量”時,添加如下對應的屬性:
__attribute__((section(".FW_RAM"))) uint8_t key[8] = {0,1,2,3,4,5,6,7 };
變量將位于“0x20000000 ~ 0x200002FF”區域(如果僅僅只有key數組位于該區域,將從0x20000000開始存放,如果有多個變量存儲于該區域,將按照編譯的順序,從0x20000000依次存放)。
指定“函數”的輸出地址:
可以定義如下的memory和section,然后將“函數”存放于該section,就能控制“函數”的輸出地址。
左右滑動查看完整內容
/* Memories definition */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 0x300 /* 0x08000000 ~ 0x080002FF */ CG_FLASH (rx) : ORIGIN = 0x08000300, LENGTH = 0x134 /* 0x08000300 ~ 0x08000433 */ RAM (xrw) : ORIGIN = 0x20000300, LENGTH = 0x900 /* 0x20000300 ~ 0x20001FFF */ } SECTIONS { … .SE_Call_Fun: { . = ALIGN(4); . = . + 0x4; *(.SE_Call_Fun) . = ALIGN(4); } > CG_FLASH … }
同時在c文件中, 在“函數”的實現部分,添加如下對應的屬性:
__attribute__((section(".SE_Call_Fun"))) uint32_t call_fun(Callgate_Func_Type_t ftype, void *param)
函數“call_fun”將存放于0x08000304處(留意此處的位置計數器將產生0x04的內存間隙)。
指定“文件”輸出地址:
可以定義如下的memory和section,然后將指定的文件存放于該section,就能控制“文件”的輸出地址。
左右滑動查看完整內容
/* Memories definition */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 0x300 /* 0x08000000 ~ 0x080002FF */ FW_FLASH (rx) : ORIGIN = 0x08000434, LENGTH = 0x2BCC/* 0x08000434 ~ 0x08003000 */ RAM (xrw) : ORIGIN = 0x20000300, LENGTH = 0x900 /* 0x20000300 ~ 0x20001FFF */ } /* Sections */ SECTIONS { … .main_section : { . = ALIGN(4); Core/Src/main.o(.text*) . = ALIGN(4); } >FLASH … }
示例中將main.o指定到FLASH區域中;更改FLASH的地址或者main_section的LMA,就可以實現將特定文件指定到特定內存區域。
案例:RZ/N2L把 .text, .data, .bss段從ATCM改到SYSTEM_RAM
這里描述的RZ/N2L的內存分配:
左右滑動查看完整內容
長按可保存查看大圖
把.text段從ATCM改到SYSTEM_RAM:
左右滑動查看完整內容
長按可保存查看大圖
把.data段從ATCM改到SYSTEM_RAM:
左右滑動查看完整內容
長按可保存查看大圖
.bss段的改動也是類似的:
左右滑動查看完整內容
長按可保存查看大圖
-
嵌入式
+關注
關注
5083文章
19131瀏覽量
305536 -
程序
+關注
關注
117文章
3787瀏覽量
81073 -
腳本
+關注
關注
1文章
390瀏覽量
14879
原文標題:e2 studio中鏈接腳本的修改指導(通用)
文章出處:【微信號:瑞薩MCU小百科,微信公眾號:瑞薩MCU小百科】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論