上一章总结完了装载共享文件的源码分析,之前讲到过,rt-thread中目前可支持共享文件和可重定位文件,这也是这一章的目的。
可重定位文件简单可理解为.o文件,包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。
在rt-thread中,这个装载过程由函数_load_relocated_object来实现,总体上装载可重定位文件简单可分为三步:
一般而言,程序在内存中的镜像主要包含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);
由以上源码可知,到目前为止,程序仅只上分配了一个内存镜像并将其初始化了而已。
拷贝数据主要是将之前所提到的四类段所对应的数据拷贝到内存镜像中,四个段的过程略微有所不同:
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段在内存镜像中的地址,以供后续的重定位操作所用。
除此之外,程序还设置了模块的入口地址.
重定位的操作实际上就是将已经拷贝到内存镜像中的数据做部分修改,这个过程是根据可重定位节区来操作的。
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的符号重定位,其余的将根据符号名称到内核中找到模块再重定位。
完!