最近,有個好學(xué)的小伙子突然問了我一個問題: 全局變量的初始值,是在哪里賦值的? 這個問題雖然說不是很重要,但是我很好奇。 為了給講清楚這個原理過程,我專門建立一個基于Renesas RH850的簡單工程,挖一挖里面的技術(shù)細(xì)節(jié)。 我在main.c文件中定義了隨便這幾個變量
int counter, accumulator = 0, limit_value = 1000000; unsigned char str_aa55[2] = {0xAA,0x55}; unsigned int int_1122334455667788 = 0x11223344; unsigned int int_55667788 = 0x55667788; int bss_val; void main(void) { ? ? }然后,直接仿真查看,跟你我想的一樣,在main函數(shù)之前就初始化完成了,即這些變量都自動初始化賦值了。 讓人好奇的是,它是怎么做到的? 單片機(jī)的啟動程序一般都是很簡單的,即使匯編也沒多少行,直接翻出來看看也許會知道答案。
? -- Clear local RAM ? mov ___ghs_ramstart, r6 ?-- start of local RAM ? mov ___ghs_ramend, r7 ? ?-- end of local RAM ? mov r0, r1 1: ? st.dw ? r0, 0[r6] ? addi ? ?8, r6, r6 ? cmp r7, r6 ? bl 1b ? -- Jump to the HW initialisation function ? jarl ?___lowinit, lp ? -- Jump to the initialisation functions of the library ? -- and from there to main() ? jr __start以上這段匯編,根據(jù)旁邊的注釋其實(shí)很容易理解,前半部分就是將內(nèi)存Local RAM初始化清零,即這段匯編可以見到梳理成 RAM清零--->執(zhí)行___lowinit--->執(zhí)行__start--->進(jìn)入main函數(shù) 既然前面給RAM清零了,那么此時的全局變量應(yīng)該全是0值吧,那可以推測,給全局變量賦初始值應(yīng)該是在___lowinit或__start了,但是這兩個東西是編譯環(huán)境里某個庫的,暫時看不到源碼。 但是,最終通過仿真查看變量值的方式,可以定位,給全局變量賦初始值是在__start里面。 此時,雖然我知道了它在哪里給全局變量初始化了,但是并不知道是怎樣初始化的。 我還是很好奇,本著刨根問底的精神繼續(xù)挖掘。 但是我在這個RH850的代碼工程里面是找不到這個__start的源碼內(nèi)容的,仿真看匯編折騰了半天,突然想了下,為啥不換個其他工程試試,例如試試NXP S32K的? 于是,我創(chuàng)建了一個NXP S32K1xx的代碼工程,仍然定義這幾個變量
int counter, accumulator = 0, limit_value = 1000000; unsigned char str_aa55[2] = {0xAA,0x55}; unsigned int ?int_1122334455667788 = 0x11223344; unsigned int ?int_55667788 = 0x55667788; int?bss_val;同樣的討論,直接翻啟動文件的匯編代碼
? ? /* Init .data and .bss sections */ ? ? ldr ? ? r0,=init_data_bss ? ? blx ? ? r0還是歐美的芯片簡單粗暴,不像小日子做的初始化還有藏進(jìn)庫里。這不是很明顯嘛,init_data_bss就是初始化全局變量的,以下截取了部分代碼,也很容易理解。
void init_data_bss(void) { /*?......?*/ ? ? /* Data */ ? ? data_ram ? ? ? ?= (uint8_t *)__DATA_RAM; ? ? data_rom ? ? ? ?= (uint8_t *)__DATA_ROM; ? ? data_rom_end ? ?= (uint8_t *)__DATA_END; ????/*?......?*/ ? ? /* BSS */ ? ? bss_start ? ? ? = (uint8_t *)__BSS_START; ? ? bss_end ? ? ? ? = (uint8_t *)__BSS_END; ? ? /* ...... */ ? ? ? ? /* Copy initialized data from ROM to RAM */ ? ? while (data_rom_end != data_rom) ? ? { ? ? ? ? *data_ram = *data_rom; ? ? ? ? data_ram++; ? ? ? ? data_rom++; ? ? } ? ? ? ? /* ...... */ ? ? /* Clear the zero-initialized data section */ ? ? while(bss_end != bss_start) ? ? { ? ? ? ? *bss_start = 0; ? ? ? ? bss_start++; ? ? } ????/*?......?*/ }
?
?
?
data段data_ram的初始化內(nèi)容就是從data_rom來,而data_rom是從__DATA_ROM來。 那么,__DATA_ROM是什么東西,從哪里呢? 搜一搜工程里面的代碼,很簡單,這是從ld文件來
?
/* Specify the memory areas */ MEMORY { ? /* … */ ? /* SRAM_L */ m_data ? (RW) ?: ORIGIN = 0x1FFF8000, LENGTH = 0x00008000 ?m_data_2??(RW)??:?ORIGIN?=?0x20000000,?LENGTH?=?0x00007000 ? /* … */ .data : AT(__DATA_ROM) ? { ? ? . = ALIGN(4); ? ? __DATA_RAM = .; ? ? __data_start__ = .; ? ? ?/* Create a global symbol at data start. */ ? ? *(.data) ? ? ? ? ? ? ? ? /* .data sections */ ? ? *(.data*) ? ? ? ? ? ? ? ?/* .data* sections */ ? ? . = ALIGN(4); ? ? __data_end__ = .; ? ? ? ?/* Define a global symbol at data end. */ ? } > m_data ? __DATA_END = __DATA_ROM + (__data_end__ - __data_start__); ? __CODE_ROM = __DATA_END; /* Symbol is used by code initialization. */ ? /* Uninitialized data section. */ ? .bss : ? { ? ? /* This is used by the startup in order to initialize the .bss section. */ ? ? . = ALIGN(4); ? ? __BSS_START = .; ? ? __bss_start__ = .; ? ? *(.bss) ? ? *(.bss*) ? ? *(COMMON) ? ? . = ALIGN(4); ? ? __bss_end__ = .; ? ? __BSS_END = .; ? } > m_data_2這里簡單介紹下,帶有初始化值(非0)全局變量(例如unsigned int ?int_55667788 = 0x55667788;),都是定義在data段的,而未定義初始化值的全局變量,是分在bss段的(例如int?bss_val;)。 到底是不是我說的這樣子,直接查看map文件中的變量名和對應(yīng)地址或段名就知道了
.data ? ? ? ? ? 0x1fff8400 ? ? ?0x42c load address 0x000009cc ? ? ? ? ? ? ? ? 0x1fff8400 ? ? ? ? ? ? ? ?. = ALIGN (0x4) ? ? ? ? ? ? ? ? 0x1fff8400 ? ? ? ? ? ? ? ?__DATA_RAM = . ? ? ? ? ? ? ? ? 0x1fff8400 ? ? ? ? ? ? ? ?__data_start__ = . ?*(.data) ?*(.data*) ?.data.limit_value ? ? ? ? ? ? ? ? 0x1fff8400 ? ? ? ?0x4 ./src/main.o ? ? ? ? ? ? ? ? 0x1fff8400 ? ? ? ? ? ? ? ?limit_value ?.data.str_aa55 ? ? ? ? ? ? ? ? 0x1fff8404 ? ? ? ?0x2 ./src/main.o ? ? ? ? ? ? ? ? 0x1fff8404 ? ? ? ? ? ? ? ?str_aa55 ?*fill* ? ? ? ? 0x1fff8406 ? ? ? ?0x2 ?.data.int_11223344 ? ? ? ? ? ? ? ? 0x1fff8408 ? ? ? ?0x4 ./src/main.o ? ? ? ? ? ? ? ? 0x1fff8408 ? ? ? ? ? ? ? ?int_11223344 ?.data.int_55667788 ? ? ? ? ? ? ? ? 0x1fff840c ? ? ? ?0x4 ./src/main.o ? ? ? ? ? ? ? ? 0x1fff840c ? ? ? ? ? ? ? ?int_55667788 .bss ? ? ? ? ? ?0x20000000 ? ? ? 0x28 ? ? ? ? ? ? ? ? 0x20000000 ? ? ? ? ? ? ? ?. = ALIGN (0x4) ? ? ? ? ? ? ? ? 0x20000000 ? ? ? ? ? ? ? ?__BSS_START = . ? ? ? ? ? ? ? ? 0x20000000 ? ? ? ? ? ? ? ?__bss_start__ = . ?*(.bss) ?*(.bss*) ?.bss.accumulator ? ? ? ? ? ? ? ? 0x2000001c ? ? ? ?0x4 ./src/main.o ? ? ? ? ? ? ? ? 0x2000001c ? ? ? ? ? ? ? ?accumulator ?*(COMMON) ?COMMON ? ? ? ? 0x20000020 ? ? ? ?0x8 ./src/main.o ? ? ? ? ? ? ? ? 0x20000020 ? ? ? ? ? ? ? ?bss_val ? ? ? ? ? ? ? ? 0x20000024 ? ? ? ? ? ? ? ?counter ? ? ? ? ? ? ? ? 0x20000028 ? ? ? ? ? ? ? ?. = ALIGN (0x4) ? ? ? ? ? ? ? ? 0x20000028 ? ? ? ? ? ? ? ?__bss_end__ = . ????????????????0x20000028????????????????__BSS_END?=?. ????????????????0x000009cc????????????????__DATA_ROM?=?.? 另外,從這map文件里也可以看到,這個__DATA_ROM對應(yīng)的地址是0x000009cc,也就是說,這些
int limit_value = 1000000; unsigned char str_aa55[2] = {0xAA,0x55}; unsigned int int_1122334455667788 = 0x11223344; unsigned int int_55667788 = 0x55667788;等等變量的的初始值是來源于0x000009cc這里。 那就直接查看生成的hex文件
?
?
是不是很巧,是不是很妙?! 不過,還是很好奇,這是怎么做到的,怎么恰巧這些值就在這個地址呢? 秘密就在于ld文件里的這個語句:.data : AT(__DATA_ROM) 意思是,定義在data段的變量對應(yīng)的初始化值,就放在__DATA_ROM中。就這么簡單,剩下的交給編譯器就行了。 這里順便提一下,排查跟蹤這些信息需要掌握編譯鏈接相關(guān)知識,特別是鏈接腳本和生成的map內(nèi)容,我之前也跟我身邊的很多小伙伴講過這些內(nèi)容,但是我建議系統(tǒng)地看看這些內(nèi)容。我之前是無意中從《程序員的自我修養(yǎng)》這本書學(xué)到的,最大的感受就是讓我得到了啟發(fā),在往后的編程日子里更關(guān)注MCU地址、編譯和鏈接方面的內(nèi)容,對排查底層問題相當(dāng)有幫助。
S32K1的工程很清楚了,但是RH850的呢?一根筋的程序員,還是想知道__start到底干了啥?它所在的庫又是怎么鏈接進(jìn)來的,是怎么初始化的? 對這個問題一直記在心里,是一個化不開的結(jié)。終于某一天,我居然真的無意中發(fā)現(xiàn)了__start的源碼,感嘆小日子真的有一手。
編輯:黃飛
?
評論
查看更多