1 前言
上一章總結完了裝載共用檔案的源碼分析,之前講到過,rt-thread中目前可支援共用檔案和可重定位檔案,這也是這一章的目的。
可重定位檔案簡單可理解為.o檔案,包含適合於與其他目標檔案連結來建立可執行檔或者共用目標檔案的代碼和資料。
在rt-thread中,這個裝載過程由函數_load_relocated_object來實現,總體上裝載可重定位檔案簡單可分為三步:
- 計算記憶體鏡像大小,並分配記憶體。
- 拷貝資料到記憶體鏡像。
- 重定位記憶體鏡像中的資料。
2 計算記憶體鏡像大小並分配記憶體
一般而言,程式在記憶體中的鏡像主要包含text(code即代碼),rodata(唯讀資料,指常量),data(可讀寫資料,一般即已初始化全域變數,不包含初始化為0的),bss(未初始化的全域變數)。因此,程式開始就計算出這四個段在記憶體鏡像中所佔的大小:
//遍曆elf檔案中的所有節區頭部表 for (index = 0; index < elf_module->e_shnum; index ++) { /* text */ if (IS_PROG(shdr[index]) && IS_AX(shdr[index]))//如果是text節區頭部表,即代碼 { module_size += shdr[index].sh_size; //增加記憶體鏡像大小 module_addr = shdr[index].sh_addr; //記錄下程式碼片段的地址 } /* rodata */ if (IS_PROG(shdr[index]) && IS_ALLOC(shdr[index]))//如果是唯讀資料 { module_size += shdr[index].sh_size;//增加記憶體鏡像大小 } /* data */ if (IS_PROG(shdr[index]) && IS_AW(shdr[index]))//如果是可讀寫資料,即已初始化的全域變數區 { module_size += shdr[index].sh_size; //增加記憶體鏡像大小 } /* bss */ if (IS_NOPROG(shdr[index]) && IS_AW(shdr[index]))//如果是未初始化的全域變數 { module_size += shdr[index].sh_size; //增加記憶體鏡像大小 } } /* no text, data and bss on image */ if (module_size == 0) return RT_NULL;
接下來為記憶體鏡像分配空間,並初始化:
module = (struct rt_module *) rt_object_allocate(RT_Object_Class_Module, (const char *)name);//動態分配一個模組核心對像 if (module == RT_NULL) return RT_NULL; /* allocate module space */ module->module_space = rt_malloc(module_size);//根據之前計算的記憶體鏡像大小分配空間 if (module->module_space == RT_NULL) { rt_kprintf("Module: allocate space failed.\n"); rt_object_delete(&(module->parent)); return RT_NULL; } /* zero all space */ ptr = module->module_space; //將分配的記憶體鏡像空間全部清空 rt_memset(ptr, 0, module_size);
由以上源碼可知,到目前為止,程式僅只上分配了一個記憶體鏡像並將其初始化了而已。
3 拷貝資料到記憶體鏡像
拷貝資料主要是將之前所提到的四類段所對應的資料拷貝到記憶體鏡像中,四個段的過程略微有所不同:
for (index = 0; index < elf_module->e_shnum; index ++)//遍曆所有節區頭部表 { /* load text section */ if (IS_PROG(shdr[index]) && IS_AX(shdr[index]))//如果當前為text段 { rt_memcpy(ptr, (rt_uint8_t *)elf_module + shdr[index].sh_offset, //將text段所對應的所有資料拷貝到記憶體鏡像 shdr[index].sh_size); RT_DEBUG_LOG(RT_DEBUG_MODULE, ("load text 0x%x, size %d\n", ptr, shdr[index].sh_size)); ptr += shdr[index].sh_size; } /* load rodata section */ if (IS_PROG(shdr[index]) && IS_ALLOC(shdr[index]))//如果當前為rodata段 { rt_memcpy(ptr, (rt_uint8_t *)elf_module + shdr[index].sh_offset,//將rodata段所對就的資料拷貝到記憶體鏡像 shdr[index].sh_size); rodata_addr = (rt_uint32_t)ptr; //記錄下記憶體鏡像中的唯讀資料地址 RT_DEBUG_LOG(RT_DEBUG_MODULE, ("load rodata 0x%x, size %d, rodata 0x%x\n", ptr, shdr[index].sh_size, *(rt_uint32_t *)data_addr)); ptr += shdr[index].sh_size; } /* load data section */ if (IS_PROG(shdr[index]) && IS_AW(shdr[index])) //如果當前為可讀寫資料區段 { rt_memcpy(ptr, (rt_uint8_t *)elf_module + shdr[index].sh_offset, //將data段所對應的資料拷貝到記憶體鏡像中 shdr[index].sh_size); data_addr = (rt_uint32_t)ptr; //記錄下記憶體鏡像中data段所在地址 RT_DEBUG_LOG(RT_DEBUG_MODULE, ("load data 0x%x, size %d, data 0x%x\n", ptr, shdr[index].sh_size, *(rt_uint32_t *)data_addr)); ptr += shdr[index].sh_size; } /* load bss section */ if (IS_NOPROG(shdr[index]) && IS_AW(shdr[index])) //如果當前是bss段 { rt_memset(ptr, 0, shdr[index].sh_size); //將記憶體鏡像中對應bss段的空間全部清空 bss_addr = (rt_uint32_t)ptr; RT_DEBUG_LOG(RT_DEBUG_MODULE, ("load bss 0x%x, size %d,\n", ptr, shdr[index].sh_size)); } } /* set module entry */ module->module_entry = //設定模組的入口地址 (rt_uint8_t *)module->module_space + elf_module->e_entry - module_addr;
由上面代碼可知,程式對text段,rodata段,data段都將各自對應的資料拷貝到記憶體鏡像中,而只在bss段並沒有,因為bss段表示的是未初始化的全域變數(也包含初始化為0的全域變數),程式只是將記憶體鏡像中bss段對應的空間全部清零。在拷貝資料的過程中,程式還同時記錄下了rodata段和data段,bss段在記憶體鏡像中的地址,以供後續的重定位操作所用。
除此之外,程式還設定了模組的入口地址.
4 重定位記憶體鏡像中的資料
重定位的操作實際上就是將已經拷貝到記憶體鏡像中的資料做部分修改,這個過程是根據可重定位節區來操作的。
for (index = 0; index < elf_module->e_shnum; index ++)//掃描所有節區頭部表 { rt_uint32_t i, nr_reloc; Elf32_Sym *symtab; Elf32_Rel *rel; if (!IS_REL(shdr[index]))//只操作可重定位節區頭部表項 continue; /* get relocate item */ rel = (Elf32_Rel *)((rt_uint8_t *)module_ptr + shdr[index].sh_offset);//指向可重定位節區 /* locate .dynsym and .dynstr */ symtab = (Elf32_Sym *)((rt_uint8_t *)module_ptr + //得到其符號表 shdr[shdr[index].sh_link].sh_offset); //得到其字串表 strtab = (rt_uint8_t *)module_ptr + shdr[shdr[shdr[index].sh_link].sh_link].sh_offset; shstrab = (rt_uint8_t *)module_ptr + //得到全域的字串表區 shdr[elf_module->e_shstrndx].sh_offset; nr_reloc = (rt_uint32_t)(shdr[index].sh_size / sizeof(Elf32_Rel)); //得到可重定位表項個數 /* relocate every items */ for (i = 0; i < nr_reloc; i ++)//遍曆所有可重定位表項 { Elf32_Sym *sym = &symtab[ELF32_R_SYM(rel->r_info)]; //得到其符號 RT_DEBUG_LOG(RT_DEBUG_MODULE, ("relocate symbol: %s\n", strtab + sym->st_name)); if (sym->st_shndx != STN_UNDEF) { if ((ELF_ST_TYPE(sym->st_info) == STT_SECTION) || //如果當前的符號類型表示的是資料 (ELF_ST_TYPE(sym->st_info) == STT_OBJECT)) { if (rt_strncmp((const char *)(shstrab + //符號名為rodata? shdr[sym->st_shndx].sh_name), ELF_RODATA, 8) == 0) { /* relocate rodata section */ RT_DEBUG_LOG(RT_DEBUG_MODULE, ("rodata\n")); //重定位記憶體鏡像中的rodata資料 rt_module_arm_relocate(module, rel, (Elf32_Addr)(rodata_addr + sym->st_value)); } else if (rt_strncmp((const char*) (shstrab + shdr[sym->st_shndx].sh_name), ELF_BSS, 5) == 0)//符號為bss? { /* relocate bss section */ RT_DEBUG_LOG(RT_DEBUG_MODULE, ("bss\n")); rt_module_arm_relocate(module, rel, (Elf32_Addr)bss_addr + sym->st_value);//重定位記憶體鏡像中的bss } else if (rt_strncmp((const char *)(shstrab + shdr[sym->st_shndx].sh_name),//符號為data? ELF_DATA, 6) == 0) { /* relocate data section */ RT_DEBUG_LOG(RT_DEBUG_MODULE, ("data\n")); rt_module_arm_relocate(module, rel, (Elf32_Addr)data_addr + sym->st_value);//重定位記憶體鏡像中的data } } } else if (ELF_ST_TYPE(sym->st_info) == STT_FUNC) //如果為函數符號 { /* relocate function */ rt_module_arm_relocate(module, rel, (Elf32_Addr)((rt_uint8_t *)//重定位記憶體鏡像中的相應地址 module->module_space - module_addr + sym->st_value)); } else //其它類型的符號 { Elf32_Addr addr; if (ELF32_R_TYPE(rel->r_info) != R_ARM_V4BX)//類型類型不為R_ARM_V4BX? { RT_DEBUG_LOG(RT_DEBUG_MODULE, ("relocate symbol: %s\n", strtab + sym->st_name)); /* need to resolve symbol in kernel symbol table */ addr = rt_module_symbol_find((const char *)(strtab + sym->st_name));//在核心中尋找對應的模組 if (addr != (Elf32_Addr)RT_NULL) { rt_module_arm_relocate(module, rel, addr);//重定位此模組 RT_DEBUG_LOG(RT_DEBUG_MODULE, ("symbol addr 0x%x\n", addr)); } else rt_kprintf("Module: can't find %s in kernel symbol table\n", strtab + sym->st_name); } else { rt_module_arm_relocate(module, rel, (Elf32_Addr)((rt_uint8_t*)//重定位此模組 module->module_space - module_addr + sym->st_value)); } } rel ++;//下一可重定位表項 } }
可重定位操作大體是是根據可重定位表項將rodata,data,bss,函數,R_ARM_V4BX的符號重定位,其餘的將根據符號名稱到核心中找到模組再重定位。
完!