1 前言
上一章总结完了装载共享文件的源码分析,之前讲到过,rt-thread中目前可支持共享文件和可重定位文件,这也是这一章的目的。
可重定位文件简单可理解为.o文件,包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。
在rt-thread中,这个装载过程由函数_load_relocated_object来实现,总体上装载可重定位文件简单可分为三步:
-
计算内存镜像大小,并分配内存。
-
拷贝数据到内存镜像。
-
重定位内存镜像中的数据。
2 计算内存镜像大小并分配内存
一般而言,程序在内存中的镜像主要包含text(code即代码),rodata(只读数据,指常量),data(可读写数据,一般即已初始化全局变量,不包含初始化为0的),bss(未初始化的全局变量)。因此,程序开始就计算出这四个段在内存镜像中所占的大小:
-
-
for (index = 0; index < elf_module->e_shnum; index ++)
-
{
-
-
if (IS_PROG(shdr[index]) && IS_AX(shdr[index]))
-
{
-
module_size += shdr[index].sh_size;
-
module_addr = shdr[index].sh_addr;
-
}
-
-
if (IS_PROG(shdr[index]) && IS_ALLOC(shdr[index]))
-
{
-
module_size += shdr[index].sh_size;
-
}
-
-
if (IS_PROG(shdr[index]) && IS_AW(shdr[index]))
-
{
-
module_size += shdr[index].sh_size;
-
}
-
-
if (IS_NOPROG(shdr[index]) && IS_AW(shdr[index]))
-
{
-
module_size += shdr[index].sh_size;
-
}
-
}
-
-
-
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;
-
-
-
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;
-
}
-
-
-
ptr = module->module_space;
-
rt_memset(ptr, 0, module_size);
由以上源码可知,到目前为止,程序仅只上分配了一个内存镜像并将其初始化了而已。
3 拷贝数据到内存镜像
拷贝数据主要是将之前所提到的四类段所对应的数据拷贝到内存镜像中,四个段的过程略微有所不同:
-
for (index = 0; index < elf_module->e_shnum; index ++)
-
{
-
-
if (IS_PROG(shdr[index]) && IS_AX(shdr[index]))
-
{
-
rt_memcpy(ptr,
-
(rt_uint8_t *)elf_module + shdr[index].sh_offset,
-
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;
-
}
-
-
-
if (IS_PROG(shdr[index]) && IS_ALLOC(shdr[index]))
-
{
-
rt_memcpy(ptr,
-
(rt_uint8_t *)elf_module + shdr[index].sh_offset,
-
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;
-
}
-
-
-
if (IS_PROG(shdr[index]) && IS_AW(shdr[index]))
-
{
-
rt_memcpy(ptr,
-
(rt_uint8_t *)elf_module + shdr[index].sh_offset,
-
shdr[index].sh_size);
-
data_addr = (rt_uint32_t)ptr;
-
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;
-
}
-
-
-
if (IS_NOPROG(shdr[index]) && IS_AW(shdr[index]))
-
{
-
rt_memset(ptr, 0, shdr[index].sh_size);
-
bss_addr = (rt_uint32_t)ptr;
-
RT_DEBUG_LOG(RT_DEBUG_MODULE, ("load bss 0x%x, size %d,\n",
-
ptr, shdr[index].sh_size));
-
}
-
}
-
-
-
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;
-
-
-
rel = (Elf32_Rel *)((rt_uint8_t *)module_ptr + shdr[index].sh_offset);
-
-
-
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));
-
-
-
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 +
-
shdr[sym->st_shndx].sh_name), ELF_RODATA, 8) == 0)
-
{
-
-
RT_DEBUG_LOG(RT_DEBUG_MODULE, ("rodata\n"));
-
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)
-
{
-
-
RT_DEBUG_LOG(RT_DEBUG_MODULE, ("bss\n"));
-
rt_module_arm_relocate(module, rel,
-
(Elf32_Addr)bss_addr + sym->st_value);
-
}
-
else if (rt_strncmp((const char *)(shstrab + shdr[sym->st_shndx].sh_name),
-
ELF_DATA, 6) == 0)
-
{
-
-
RT_DEBUG_LOG(RT_DEBUG_MODULE, ("data\n"));
-
rt_module_arm_relocate(module, rel,
-
(Elf32_Addr)data_addr + sym->st_value);
-
}
-
}
-
}
-
else if (ELF_ST_TYPE(sym->st_info) == STT_FUNC)
-
{
-
-
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)
-
{
-
RT_DEBUG_LOG(RT_DEBUG_MODULE, ("relocate symbol: %s\n",
-
strtab + sym->st_name));
-
-
-
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的符号重定位,其余的将根据符号名称到内核中找到模块再重定位。