Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 146 additions & 9 deletions components/libc/posix/libdl/dlelf.c
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,48 @@ rt_err_t dlmodule_load_shared_object(struct rt_dlmodule* module, void *module_pt
return RT_EOK;
}

/**
* @brief Compute the size of the common block of a symbol table, and
* optionally the offset of one specific SHN_COMMON symbol inside it.
*
* SHN_COMMON symbols are tentative definitions of uninitialized global
* variables. The loader must allocate storage for them just like a static
* linker does: every common symbol is aligned to its st_value alignment
* requirement and occupies st_size bytes, packed one after another.
*
* @param symtab pointer to the symbol table
* @param nsym number of symbols in the table
* @param target symbol whose offset is wanted, or RT_NULL to get the total
* size of the common block
* @return offset of target in the common block, or the total size of the
* block when target is RT_NULL
*/
static rt_ubase_t dlmodule_common_block(Elf_Sym *symtab, rt_ubase_t nsym,
Elf_Sym *target)
{
rt_ubase_t i, offset = 0;

for (i = 0; i < nsym; i++)
{
if (symtab[i].st_shndx != SHN_COMMON)
{
continue;
}

/* st_value of a SHN_COMMON symbol holds its alignment requirement */
offset = RT_ALIGN(offset, symtab[i].st_value > 1 ? symtab[i].st_value : 1);

if (target == &symtab[i])
{
return offset;
}

offset += symtab[i].st_size;
}

return offset;
}

/**
* @brief Load a relocatable file into memory.
*
Expand All @@ -319,7 +361,7 @@ rt_err_t dlmodule_load_shared_object(struct rt_dlmodule* module, void *module_pt
*/
rt_err_t dlmodule_load_relocated_object(struct rt_dlmodule* module, void *module_ptr)
{
rt_ubase_t index, rodata_addr = 0, bss_addr = 0, data_addr = 0;
rt_ubase_t index, rodata_addr = 0, bss_addr = 0, data_addr = 0, common_addr = 0;
rt_ubase_t module_addr = 0, module_size = 0;
rt_uint8_t *ptr, *strtab, *shstrab;

Expand Down Expand Up @@ -349,6 +391,23 @@ rt_err_t dlmodule_load_relocated_object(struct rt_dlmodule* module, void *module
}
}

/* account for common symbols (uninitialized global variables) */
for (index = 0; index < elf_module->e_shnum; index++)
{
Elf_Sym *symtab;
rt_ubase_t nsym;

if (shdr[index].sh_type != SHT_SYMTAB)
{
continue;
}

symtab = (Elf_Sym *)((rt_uint8_t *)elf_module + shdr[index].sh_offset);
nsym = (rt_ubase_t)(shdr[index].sh_size / sizeof(Elf_Sym));
module_size += RT_ALIGN(dlmodule_common_block(symtab, nsym, RT_NULL),
sizeof(rt_ubase_t));
}

/* no text, data and bss on image */
if (module_size == 0) return RT_NULL;

Expand Down Expand Up @@ -410,9 +469,14 @@ rt_err_t dlmodule_load_relocated_object(struct rt_dlmodule* module, void *module
rt_memset(ptr, 0, shdr[index].sh_size);
bss_addr = (rt_ubase_t)ptr;
LOG_D("load bss 0x%x, size %d", ptr, shdr[index].sh_size);
ptr += shdr[index].sh_size;
}
}

/* the common block (uninitialized global variables) starts right after
* the loaded sections; it was zeroed together with the module space */
common_addr = (rt_ubase_t)ptr;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] 对齐 COMMON 块基址,并为填充预留空间

这里直接将已加载段的结束地址作为 common_addr,但 dlmodule_common_block() 只对齐相对于块起点的偏移,不能保证最终符号地址满足 st_value 的对齐要求。使用当前加载函数复现:8 字节 text 后接 1 字节 data,要求 4 字节对齐的 COMMON 变量被放在 mem_space + 9,最终地址模 4 为 1。这会让要求对齐的访问在目标平台上产生异常。请按 COMMON 符号的最大对齐要求对齐块基址,并在 module_size 中预留相应填充空间;只对齐块大小不足以解决此问题。


/* set module entry */
module->entry_addr = (rt_dlmodule_entry_func_t)((rt_uint8_t *)module->mem_space + elf_module->e_entry - module_addr);

Expand Down Expand Up @@ -459,29 +523,48 @@ rt_err_t dlmodule_load_relocated_object(struct rt_dlmodule* module, void *module
{
Elf_Addr addr = 0;

if ((ELF_ST_TYPE(sym->st_info) == STT_SECTION) ||
(ELF_ST_TYPE(sym->st_info) == STT_OBJECT))
if (sym->st_shndx == SHN_COMMON)
{
/* relocate common symbol (uninitialized global variable):
* SHN_COMMON is not a real section index, so it must be
* handled before the section-name checks below */
LOG_D("common symbol");
addr = (Elf_Addr)(common_addr + dlmodule_common_block(
symtab,
(rt_ubase_t)(shdr[shdr[index].sh_link].sh_size / sizeof(Elf_Sym)),
sym));
dlmodule_relocate(module, rel, addr);
}
else if ((ELF_ST_TYPE(sym->st_info) == STT_SECTION) ||
(ELF_ST_TYPE(sym->st_info) == STT_OBJECT))
{
Elf_Addr sec_sh_addr = shdr[sym->st_shndx].sh_addr;
if (rt_strncmp((const char *)(shstrab +
shdr[sym->st_shndx].sh_name), ELF_RODATA, 8) == 0)
shdr[sym->st_shndx].sh_name),
ELF_RODATA, 8) == 0)
{
/* relocate rodata section */
LOG_D("rodata");
addr = (Elf_Addr)(rodata_addr + sym->st_value);
addr = (Elf_Addr)(rodata_addr - sec_sh_addr + sym->st_value);
}
else if (rt_strncmp((const char *)
(shstrab + shdr[sym->st_shndx].sh_name), ELF_BSS, 5) == 0)
else if (rt_strncmp((const char *)(shstrab + shdr[sym->st_shndx].sh_name), ELF_BSS, 5) == 0)
{
/* relocate bss section */
LOG_D("bss");
addr = (Elf_Addr)bss_addr + sym->st_value;
addr = (Elf_Addr)(bss_addr - sec_sh_addr + sym->st_value);
}
else if (rt_strncmp((const char *)(shstrab + shdr[sym->st_shndx].sh_name), ".common", 7) == 0)
{
/* relocate common section (uninitialized globals) */
LOG_D("common");
addr = (Elf_Addr)(bss_addr - sec_sh_addr + sym->st_value);
}
else if (rt_strncmp((const char *)(shstrab + shdr[sym->st_shndx].sh_name),
ELF_DATA, 6) == 0)
{
/* relocate data section */
LOG_D("data");
addr = (Elf_Addr)data_addr + sym->st_value;
addr = (Elf_Addr)(data_addr - sec_sh_addr + sym->st_value);
}

if (addr != 0) dlmodule_relocate(module, rel, addr);
Expand Down Expand Up @@ -533,5 +616,59 @@ rt_err_t dlmodule_load_relocated_object(struct rt_dlmodule* module, void *module
}
}

/* construct the module symbol table from .symtab so that lookups such as
* dlsym() can resolve the module's global functions */
shstrab = (rt_uint8_t *)module_ptr + shdr[elf_module->e_shstrndx].sh_offset;
for (index = 0; index < elf_module->e_shnum; index++)
{
if ((shdr[index].sh_type == SHT_SYMTAB) &&
(rt_strcmp((const char *)(shstrab + shdr[index].sh_name), ELF_SYMTAB) == 0))
{
break;
}
}

if (index != elf_module->e_shnum)
{
Elf_Sym *symtab;
rt_ubase_t i, count = 0;

symtab = (Elf_Sym *)((rt_uint8_t *)module_ptr + shdr[index].sh_offset);
strtab = (rt_uint8_t *)module_ptr + shdr[shdr[index].sh_link].sh_offset;

for (i = 0; i < shdr[index].sh_size / sizeof(Elf_Sym); i++)
{
if ((ELF_ST_BIND(symtab[i].st_info) == STB_GLOBAL) &&
(ELF_ST_TYPE(symtab[i].st_info) == STT_FUNC))
{
count++;
}
}

module->symtab = (struct rt_module_symtab *)rt_malloc(count * sizeof(struct rt_module_symtab));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] 检查符号表和符号名称的分配结果

新增的符号表数组分配没有检查返回值,后续会直接写入 module->symtab[count];第 664 行的名称分配也未经检查就传给 rt_memset()。在模块空间分配成功后,分别令这两处 rt_malloc() 返回空指针,均复现了空指针访问,低内存时会使模块加载触发系统异常。请检查两处分配结果,失败时清理已分配资源并返回错误;同时让 module->nsym 只统计已完成初始化的条目,避免调用方的 dlmodule_destroy() 遍历未初始化项。

module->nsym = count;
for (i = 0, count = 0; i < shdr[index].sh_size / sizeof(Elf_Sym); i++)
{
rt_size_t length;

if ((ELF_ST_BIND(symtab[i].st_info) != STB_GLOBAL) ||
(ELF_ST_TYPE(symtab[i].st_info) != STT_FUNC))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 从导出表中排除未定义函数

这里仅检查 STB_GLOBAL 和 STT_FUNC,因此也会收集 st_shndx == SHN_UNDEF 的外部函数引用。构造一个 GLOBAL/FUNC/UNDEF、st_value = 0 的符号后,当前代码将其加入导出表,dlsym() 返回模块基址,而该函数并没有在此模块中定义;调用这个地址会执行错误代码。请在前面的计数循环及这里的填表循环中同时排除未定义符号,避免将导入引用作为模块自身的函数导出。

{
continue;
}

length = rt_strlen((const char *)(strtab + symtab[i].st_name)) + 1;

module->symtab[count].addr =
(void *)((rt_uint8_t *)module->mem_space - module_addr + symtab[i].st_value);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 根据符号所属段计算导出函数地址

ET_REL 中已定义符号的 st_value 是 st_shndx 所指段内的偏移,不能统一加到模块基址上。使用两个各 8 字节的可执行段、每段一个 st_value = 0 的全局函数复现时,两个导出地址都变成了 mem_space,而第二个函数实际位于 mem_space + 8。保留多个代码段的模块(例如使用 -ffunction-sections 后进行可重定位链接)会因此让 dlsym() 返回错误地址;若涉及 module_init,加载阶段就可能调用错误函数。请记录各段的实际加载地址,再按 st_shndx 和段内偏移计算导出地址。

module->symtab[count].name = rt_malloc(length);
rt_memset((void *)module->symtab[count].name, 0, length);
rt_memcpy((void *)module->symtab[count].name,
strtab + symtab[i].st_name,
length);
count++;
}
}

return RT_EOK;
}
1 change: 1 addition & 0 deletions components/libc/posix/libdl/dlelf.h
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ typedef struct
#define STT_HIPROC 15

#define STN_UNDEF 0 /* undefined */
#define SHN_COMMON 0xfff2 /* common symbols (uninitialized globals) */

#define ELF_ST_BIND(info) ((info) >> 4)
#define ELF_ST_TYPE(info) ((info) & 0xf)
Expand Down
Loading