最近,有个好学的小伙子突然问了我一个问题:
全局变量的初始值,是在哪里赋值的?
问题虽然说不是很重要,但是我很好奇。
为了给讲清楚这个原理过程,我专门建立一个基于Renesas RH850的简单工程,挖一挖里面的技术细节。
我在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) { }
-- 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
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还是欧美的芯片简单粗暴,不像小日子做的初始化还有藏进库里。这不是很明显嘛,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的初始化内容就是从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
.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 = .
int limit_value = 1000000; unsigned char str_aa55[2] = {0xAA,0x55}; unsigned int int_1122334455667788 = 0x11223344; unsigned int int_55667788 = 0x55667788;
是不是很巧,是不是很妙?!
不过,还是很好奇,这是怎么做到的,怎么恰巧这些值就在这个地址呢?
秘密就在于ld文件里的这个语句:.data : AT(__DATA_ROM)
意思是,定义在data段的变量对应的初始化值,就放在__DATA_ROM中。就这么简单,剩下的交给编译器就行了。
这里顺便提一下,排查跟踪这些信息需要掌握编译链接相关知识,特别是链接脚本和生成的map内容,我之前也跟我身边的很多小伙伴讲过这些内容,但是我建议系统地看看这些内容。我之前是无意中从《程序员的自我修养》这本书学到的,最大的感受就是让我得到了启发,在往后的编程日子里更关注MCU地址、编译和链接方面的内容,对排查底层问题相当有帮助。
S32K1的工程很清楚了,但是RH850的呢?一根筋的程序员,还是想知道__start到底干了啥?它所在的库又是怎么链接进来的,是怎么初始化的?
对这个问题一直记在心里,是一个化不开的结。终于某一天,我居然真的无意中发现了__start的源码,感叹小日子真的有一手。
这里涉及到的内容比较多也比较复杂,后续再写个文章讲解下GreenHills编译器的启动链接库到底干了什么,敬请关注!
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !