2011年8月17日 星期三

avr-gcc之section與relocation

      粗略的講,一個段代表一無縫隙的資料塊(位址範圍),一個段裏存儲的資料都為同一性質,如“唯讀”數據。as (彙編器)在編譯局部程式時總假設從0 位址開始,並生成目標檔。最後ld(鏈結器)在連接多個目標檔時為每一個段分配運行時(run-time)統一位址。這雖然是個簡單的解釋,卻足以說明我們為為什麼用段.

     ld 將這些資料塊正確移動到它們運行時的位址。此過程非常嚴格,資料的內部順序與長度均不能發生變化.這樣的資料單元叫做段,為段分配運行時位址叫再定位,此任務根據目標檔內的參考位址將段資料調整到運行時位址。

Avr-gcc 中彙編器生成的目標檔(object-file)至少包含四個段,分別為: .text 段、.data段、 .bss 段和.eeprom 段,它們包括了程式記憶體(FLASH)代碼,內部RAM 資料,和EEPROM 記憶體內的資料。這些段的大小決定了程式記憶體(FLASH)、資料記憶體(RAM)、EEPROM 記憶體的使用量,關係如下: 

程式記憶體(FLASH)使用量 = .text + .data

資料記憶體(RAM)使用量 = .data + .bss [+ .noinit] + stack [+ heap]

EEPROM 記憶體使用量 = .eeprom


一..text
.text 段包含程式實際執行代碼。另外,此段還包含.initN .finiN 兩種段,下面詳細討論這兩種段。

.initN 和段.finiN 是個程式塊,它不會象函數那樣返回,所以彙編或C 程式不能調用。.initN.finN 和絕對段(absolute section 提供中斷向量)構成avr-libc 應用程式運行框架,用戶編寫的應用程式在此框架中運行。

.initN

此類段包含從重定到 main()函數開始執行之間的啟動(startup)代碼。

此類段共定義 10 個分別是.init0 .init9。執行順序是從.init0 .init9

.init0此段綁定到函數__init()。用戶可重載__init(),復位後立即跳到該函數。

.init1未用,用戶可定義

.init2將棧指標初始化成器件對應 RAMEND 處,清零__zero_reg__寄存器

.init3未用,用戶可定義

.init4初始化.data 段(從FLASH 複製全局或靜態變數初始值到.data),清零.bss 段。

UNIX 一樣.data 段直接從可執行檔中裝入。Avr-gcc .data 段的初始值存儲到flash rom .text 段後,.init4 代碼則負責將這些資料複製SRAM .data 段。

.init5未用,用戶可定義

.init6C 代碼未用,C++程式的構造代碼

.init7未用,用戶可定義

.init8未用,用戶可定義

.init9跳到 main()

avr-libc 應用程式運行框架是以靜態庫的形式存儲在 安裝目錄>\avr\lib 目錄下,在用戶程式的連接時它們會自動連接進來。

.finiN

此類段包含 main()函數退出後執行的代碼。此類段可有 0 9 , 執行次序是從fini9 fini1

.fini9 : 此段綁定到函數exit()。用戶可重載exit()main 函數一旦退出exit 就會被執行。

.fini8未用,用戶可定義

.fini7未用,用戶可定義

.fini6C 代碼未用, C++程式的析構代碼

.fini5未用,用戶可定義

.fini4未用,用戶可定義

.fini3未用,用戶可定義

.fini2未用,用戶可定義

.fini1未用,用戶可定義

.fini0進入一個無限迴圈。

用戶代碼插入到.initN .finiN

可以將用戶代碼插入到.initN .finiN 段當中,這是通過給C 函數指定段屬性來實現的,如下是一段插入到.init1 段的示例:

void my_init_portb (void) __attribute__ ((naked)) __attribute__ ((section (".init1")));//函數的原型

void my_init_portb (void)

{
PORTB= 0xff;
DDRB=0xff;
}

使用__arrribute__關鍵字為函數指定屬性要在函數的原型聲明上,而不是函數的實現例程裏。由於屬性section(“.init 1” )的指定,編譯後函數my_init_portb 生成的代碼自動插入到.init1 段中,在main 函數前就得到執行。naked 屬性確保編譯後該函數不生成返回指令,使下一個初始化段得以順序的執行。 

二..data

.data 段包含程式中被初始化的RAM 區全局或靜態變數。而對於FLASH 記憶體此段包含在程式中定義變數的初始化資料。類似如下的代碼將生成.data 段數據。 

char err_str[]=”Your program has died a horrible death!”;

struct point pt={1,1}; 

可以將.data SRAM 內的開始位址指定給連接器,這是通過給avr-gcc 命令行添加-Wl,-Tdata,addr 選項來實現的,其中addr 必須是0X800000 SRAM 實際地址。例如要將.data 段從0x1100 開始,則addr 要給出0X801100 

三..bss

沒有被初始化的 RAM 區全局或靜態變數被分配到此段,在應用程式被執行前的startup過程中這些變數被清零。 

另外,.bss 段有一個子段 .noinit , 若變數被指定到.noinit 段中則在startup 過程中不會被清零。將變數指定到.noinit 段的方法如下:

int foo __attribute__ ((section (“.noinit”))); 

由於指定到了.noinit 段中,所以不能賦初值,如同以下代碼在編譯時產生錯誤: 

int fol __attribute__((section(“.noinit”)))=0x00ff; 

利用.noinit 段變數可以在重定時判斷是否是上電復位,以下是一段示例程式: 

#include

unsigned char rstflag[5] __attribute__((section(“.noinit”));
int main(void)
{
unsigned char j;
for(j=0;j<5;j++)
{
if(rstflag[j]!=j)
break;
}
if(j<5)
{
//上電復位
for(j=0;j<5;j++)
rstflag[j]=j;
}
else
{
//其他重定源,說明RAM 資料沒有丟失
}
… …
}

由於 rstflag .noinit 段變數,重定後avr-libc 應用框架不會初始化該變數,如果沒有掉電其內容應該保持,這便是我們判斷是否是掉電復位的依據。

四..eeprom

此段存儲 EEPROM 變數。
static unsigned char eep_buffer[3] __attribute__((section(“.eeprom”)))={1,2,3}; 

在連接選項中可指定段的開始位址,如下的選項將.noinit 段指定位到RAM 記憶體0X2000 地址處。
avr-gcc ... -Wl,--section-start=.noinit=0x802000 

要注意的是,在編譯時 Avr-gcc FLASHRAM EEPROM 內的段在一個統一的地址空間內處理,flash 記憶體被定位到0 位址開始處,RAM 記憶體被定位到0x800000 開始處,eeprom 記憶體被定位到0X810000 處。所以在指定段開始位址時若是RAM 內的段或eeprom 內的段時要在實際記憶體位址前分別加上0x800000 0X810000 

除上述四個段外,自定義段因需要而可被定義。由於編譯器不知道這類段的開始位址,又稱它們為未定義段。必需在連接選項中指定自定義段的開始位址。如下例:

void MySection(void) __attribute__((section(".mysection")));

void MySection(void)
{
printf("hello avr!");
}

連接選項:
avr-gcc ... -Wl,--section-start=.mysection=0x 001c 00

這樣函數 MySection 被定位到了FLASH 記憶體0X 1C 00 處。

沒有留言:

張貼留言

注意:只有此網誌的成員可以留言。