Skip to content

[dlmodule] fix: uninitialized globals in relocatable modules - #11915

Open
usualtreee wants to merge 3 commits into
RT-Thread:masterfrom
usualtreee:fix/dlmodule-bss-common-overlap
Open

usualtreee wants to merge 3 commits into
RT-Thread:masterfrom
usualtreee:fix/dlmodule-bss-common-overlap

Conversation

@usualtreee

@usualtreee usualtreee commented Oct 21, 2026 •

Copy link
Copy Markdown

拉取/合并请求描述:(PR description)

为什么提交这份PR (why to submit this PR)

修复 issue #5705(动态模块中未初始化全局变量访问异常,2022-03 报告,至今 open)。该问题在最新 master(1c801efa)上仍可复现:dlmodule_load_relocated_object()(ET_REL 动态模块加载路径,Cortex-M 等使用 RT_USING_MODULE 的 BSP 走此路径)存在以下缺陷,导致动态模块中未初始化全局变量的地址重叠/无法重定位:

  1. BSS 段(SHT_NOBITS)加载后分配指针不推进,多个 NOBITS 段会重叠在同一块内存;
  2. 段符号重定位公式缺少段链接地址(sh_addr)修正;
  3. SHN_COMMON 符号(tentative 定义的未初始化全局变量)重定位被静默跳过(读 shdr[0xfff2] 得到垃圾段名后不匹配);
  4. 该路径从不构建 module->symtab,dlsym() 对 ET_REL 模块永远失败。

你的解决方案是什么 (what is your solution)

  1. BSS 段加载完成后推进分配指针 ptr += shdr[index].sh_size;;
  2. rodata/bss/data 段符号重定位统一为 section_load_addr - sec_sh_addr + sym->st_value,与 text 段处理方式一致;
  3. 为 SHN_COMMON 符号分配公共块(common block):尺寸核算阶段按 st_value(对齐要求)对齐、st_size 累计计入模块总大小;重定位阶段先于段名匹配处理 SHN_COMMON(避免越界读 shdr[0xfff2]),地址 = 公共块基址 + 符号在块内偏移(与尺寸核算共用同一算法,保证一致);
  4. 从 .symtab 收集 GLOBAL FUNC 符号构建 module->symtab,使 dlsym() 对 ET_REL 模块可用(与 ET_DYN 路径从 .dynsym 构建对齐)。

修改文件:components/libc/posix/libdl/dlelf.c、dlelf.h(新增 SHN_COMMON 宏)。

请提供验证的bsp和config (provide the config and bsp)

  • BSP: bsp/qemu-vexpress-a9

  • .config: 需要开启 CONFIG_RT_USING_MODULE=y(RT_USING_DFS / RT_USING_DFS_ROMFS / RT_USING_POSIX_FS 为该 BSP 默认配置,无需改动)

  • 验证方法:qemu-system-arm 运行时加载三个测试动态模块:

    • test_bss.mo:ET_DYN(文档标准 -shared -fPIC -fcommon 流程)
    • test_kp.mo:ET_DYN + 内核符号导入(rt_kprintf 经 GOT),覆盖内核符号解析路径
    • test_bss_rel.mo:ET_REL(-r、非 PIC、-fcommon;rx_sem/serial 为 SHN_COMMON),覆盖本次修复路径
      每个模块检查未初始化全局变量地址互不重叠,并做特征值写入回读(serial=0xdeadbeef、rx_sem=0xaa55)验证内存互不踩踏。
    • 修复前(master 1c801ef):test_bss_rel.mo dlsym FAILED;
    • 修复后:dlsym 成功,rx_sem=0x600d2008、serial=0x600d2004 互不重叠,读写回读通过;两个 ET_DYN 模块修复前后结果一致,无回归。
  • action: (待触发;可在 fork 仓库 Actions 中运行 manual_dist 编译 qemu-vexpress-a9 后贴链接)

当前拉取/合并请求的状态 Intent for your PR

必须选择一项 Choose one (Mandatory):

  • 本拉取/合并请求是一个草稿版本 This PR is for a code-review and is intended to get feedback
  • 本拉取/合并请求是一个成熟版本 This PR is mature, and ready to be integrated into the repo

代码质量 Code Quality:

我在这个拉取/合并请求中已经考虑了 As part of this pull request, I've considered the following:

  • 已经仔细查看过代码改动的对比 Already check the difference between PR and old code
  • 代码风格正确,包括缩进空格,命名及其他风格 Style guide is adhered to, including spacing, naming and other styles
  • 没有垃圾代码,代码尽量精简,不包含#if 0代码,不包含已经被注释了的代码 All redundant code is removed and cleaned up
  • 所有变更均有原因及合理的,并且不会影响到其他软件组件代码或BSP All modifications are justified and not affect other components or BSP
  • 对难懂代码均提供对应的注释 I've commented appropriately where code is tricky
  • 代码是高质量的 Code in this PR is of high quality
  • 已经使用clang-format 源码格式化工具确保格式符合RT-Thread代码规范 This PR has been formatted with clang-format and complies with RT-Thread code specification
  • 如果是新增bsp, 已经添加ci检查到.github/ALL_BSP_COMPILE.json 详细请参考链接BSP自查

Fix the loader of relocatable (ET_REL) dynamic modules so that
uninitialized global variables get their own storage and correct
addresses:

1. Advance the allocation pointer after loading a BSS (SHT_NOBITS)
   section, so multiple NOBITS sections no longer overlap in memory.
2. Relocate section symbols with
   section_load_addr - sec_sh_addr + sym->st_value, matching how text
   symbols are already handled.
3. Allocate a common block for SHN_COMMON symbols (tentative
   definitions of global variables) and relocate every reference to it
   instead of silently skipping them.
4. Build the module symbol table from .symtab for ET_REL modules so
   that dlsym() can resolve the module's global functions.

Verified on bsp/qemu-vexpress-a9 with CONFIG_RT_USING_MODULE=y: before
the fix an ET_REL module fails dlsym(), after the fix its globals get
correct non-overlapping addresses and round-trip reads/writes succeed,
and ET_DYN (-shared) modules are unaffected.

Refs: RT-Thread#5705
@github-actions

Copy link
Copy Markdown

👋 感谢您对 RT-Thread 的贡献!Thank you for your contribution to RT-Thread!

为确保代码符合 RT-Thread 的编码规范,请在你的仓库中执行以下步骤运行代码格式化工作流(如果格式化CI运行失败)。
To ensure your code complies with RT-Thread's coding style, please run the code formatting workflow by following the steps below (If the formatting of CI fails to run).


🛠 操作步骤 | Steps

  1. 前往 Actions 页面 | Go to the Actions page
    点击进入工作流 → | Click to open workflow →

  2. 点击 Run workflow | Click Run workflow

  • Use workflow from 保持默认分支(通常为 master)
    Keep the default branch (usually master) in Use workflow from
  • 在 branch 输入框填写 PR 分支 fix/dlmodule-bss-common-overlap
    Enter PR branch fix/dlmodule-bss-common-overlap in the branch field
  • 设置需排除的文件/目录(目录请以"/"结尾)
    Set files/directories to exclude (directories should end with "/")
  1. 等待工作流完成 | Wait for the workflow to complete
    格式化后的代码将作为独立提交推送至你的分支。
    The formatting changes will be pushed to your branch as a separate commit.

完成后,提交将自动更新至 fix/dlmodule-bss-common-overlap 分支,关联的 Pull Request 也会同步更新。
Once completed, commits will be pushed to the fix/dlmodule-bss-common-overlap branch automatically, and the related Pull Request will be updated.

如有问题欢迎联系我们,再次感谢您的贡献!💐
If you have any questions, feel free to reach out. Thanks again for your contribution!

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

📌 Code Review Assignment

🏷️ Tag: components

Reviewers: @Maihuanyi

Changed Files (Click to expand)
  • components/libc/posix/libdl/dlelf.c
  • components/libc/posix/libdl/dlelf.h

🏷️ Tag: components_libc

Reviewers: @GorrayLi @mysterywolf

Changed Files (Click to expand)
  • components/libc/posix/libdl/dlelf.c
  • components/libc/posix/libdl/dlelf.h

📊 Current Review Status (Last Updated: 2026-10-28 15:50 CST)


📝 Review Instructions

  1. 维护者可以通过单击此处来刷新审查状态: 🔄 刷新状态
    Maintainers can refresh the review status by clicking here: 🔄 Refresh Status

  2. 确认审核通过后评论 LGTM/lgtm
    Comment LGTM/lgtm after confirming approval

  3. PR合并前需至少一位维护者确认
    PR must be confirmed by at least one maintainer before merging

ℹ️ 刷新CI状态操作需要具备仓库写入权限。
ℹ️ Refresh CI status operation requires repository Write permission.

@CLAassistant

CLAassistant commented Oct 21, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

@Rbb666 Rbb666 closed this Oct 26, 2026
@Rbb666 Rbb666 reopened this Oct 26, 2026

@Rbb666 Rbb666 left a comment

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.

本次审查基于提交 b96281f。请先修复 4 条行内评论中的问题:COMMON 块基址对齐、符号表分配失败处理、按所属段计算导出函数地址,以及排除未定义函数。

验证使用了该提交中加载函数的原文,在宿主机测试程序中构造 ELF 输入,并对两处新增分配分别注入失败;共复现 5 个场景。此验证覆盖地址计算和错误处理,未进行板级或 QEMU 验证。

Comment thread components/libc/posix/libdl/dlelf.c Outdated

/* 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 中预留相应填充空间;只对齐块大小不足以解决此问题。

@usualtreee usualtreee Oct 26, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

感谢老师的细致评审。已根据建议修正:现在会记录 COMMON 符号的最大对齐要求,在 module_size 中预留相应的填充空间,并对齐 common_addr,不再只对齐块内偏移。针对 4 字节和 16 字节对齐场景的测试均已通过,麻烦老师有空再帮忙确认一下!

Comment thread components/libc/posix/libdl/dlelf.c Outdated
}
}

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() 遍历未初始化项。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

感谢老师提醒!这里确实考虑得不够周全。现已补充符号表和符号名称分配结果的检查,失败时返回 -RT_ENOMEM 并正确清理资源;同时,module->nsym 只统计完整初始化的条目,避免销毁时访问未初始化项。两类分配失败的注入测试均已通过,麻烦你再帮忙看一下!

Comment thread components/libc/posix/libdl/dlelf.c Outdated
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 和段内偏移计算导出地址。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

感谢老师。现已记录各段的实际加载地址,并根据符号的 st_shndx、sh_addr 和段内偏移计算导出函数地址。多代码段测试中,两个函数的导出地址现在均能指向各自正确的位置,麻烦您再帮忙复核一下!

Comment thread components/libc/posix/libdl/dlelf.c Outdated
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() 返回模块基址,而该函数并没有在此模块中定义;调用这个地址会执行错误代码。请在前面的计数循环及这里的填表循环中同时排除未定义符号,避免将导入引用作为模块自身的函数导出。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

感谢指出!现已在计数和填表两个循环中同时排除 SHN_UNDEF,并补充段索引范围及已加载段检查,避免将外部函数引用误作为模块自身函数导出。相关测试均已通过,纯未定义符号场景下的导出数量为 0。若还有考虑不周之处,也麻烦您继续指出。

Reserve COMMON alignment padding and check symbol-table allocations. Use per-section load addresses for exported functions and exclude undefined symbols.

@Rbb666 Rbb666 left a comment

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.

基于提交 5d6e93f933 补充三条行内审查意见,均附复现步骤和结果。验证使用当前加载代码、ARM 重定位代码及交叉编译生成的 ELF,在主机上检查地址和内存内容;未做 QEMU 或实板验证。

Comment thread components/libc/posix/libdl/dlelf.c Outdated
Comment on lines +560 to +564
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);

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 重定位需要写到目标段,不能直接使用段内偏移

ET_REL 的 r_offset 是目标段内的偏移。这里把原始重定位项直接传给 dlmodule_relocate(),而 ARM 实现按 module->mem_space + rel->r_offset 计算写入位置。引用只要不在第一个加载段,就会写错地方。

复现代码:

int common_value;
int *common_pointer = &common_value;
int function(void) { return 42; }

编译命令:

arm-none-eabi-gcc -marm -mcpu=arm926ej-s -fcommon -fno-pic -c common.c -o common.o
arm-none-eabi-ld -r common.o -o common.mo

生成的 .text 为 28 字节,随后是 4 字节的 .data;.rel.data 中引用 common_value 的项为 R_ARM_ABS32,r_offset=0。

将本提交的加载函数和 ARM 重定位函数放入主机测试程序,模块内存固定在 0x10000000,得到以下结果:

  • COMMON 变量分配在 0x10000020。
  • .data 中的 common_pointer 应写入该地址,实际仍为零。
  • .text 的第一条指令由 0xe52db004 被改成 0xf52db024。
  • 加载函数仍返回成功。

请根据重定位段的 sh_info 找到目标段,用 section_load_addr 换算写入位置后再执行重定位。上述验证检查了内存内容,未运行模块中的 ARM 指令,也未做 QEMU 或实板验证。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

感谢老师指出,已在提交 6880fad 中修复。现在根据重定位节的 sh_info 查找目标节实际加载地址,将 r_offset 换算为模块内偏移后再调用架构重定位函数。已补充 COMMON 跨节重定位测试,主机检查及 QEMU 验证均通过。

Comment thread components/libc/posix/libdl/dlelf.c Outdated
Comment on lines +578 to +588
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);

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] 多个 NOBITS 段仍被重定位到同一个 bss_addr

加载 NOBITS 段后虽然推进了 ptr,但 bss_addr 只保存最后一个 NOBITS 段的地址。这里处理 .bss 和新增的 .common 分支时都使用它,两个段中的变量仍可能指向同一块内存。

复现代码:

int bss_value = 0;
int common_value;
int get_values(void) { return bss_value + common_value; }

用下面的链接脚本把两类变量分别放入 .bss 和 .common:

SECTIONS
{
    .text 0 : { *(.text*) }
    .data 0 : { *(.data*) }
    .bss 0 : { *(.bss*) }
    .common 0 : { *(COMMON) }
}

编译命令:

arm-none-eabi-gcc -marm -mcpu=arm926ej-s -fcommon -c multibss.c -o multibss.o
arm-none-eabi-ld -r -d -T multibss.ld multibss.o -o multibss.mo

使用本提交的加载函数和 ARM 重定位函数做主机测试,结果如下,偏移均相对于 module->mem_space:

变量 所属段 实际分配偏移 重定位后引用的偏移
bss_value .bss 52 56
common_value .common 56 56

两个变量仍共用地址,写入其中一个会覆盖另一个。请按 sym->st_shndx 使用已经记录的 section_load_addr,不要再用单个 bss_addr 代表所有 NOBITS 段。本次为主机验证,未做 QEMU 或实板验证。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

感谢老师指出!已在提交 6880fad 中移除单个 bss_addr 的处理,统一通过 section_load_addr[st_shndx] + st_value 计算符号地址。已验证 .bss 和 .common 的地址独立,写入不同特征值后互不覆盖。

Comment thread components/libc/posix/libdl/dlelf.c Outdated
Comment on lines +706 to +708
module->symtab[module->nsym].addr =
(void *)(section_load_addr[symtab[i].st_shndx] -
shdr[symtab[i].st_shndx].sh_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 已是段内偏移,不应再减 sh_addr

ET_REL 中,已定义符号的 st_value 是相对于所属段起点的偏移。这里再减去 sh_addr,会在段地址非零时把导出地址算到模块之外。前面 rodata、bss、data 重定位中新增的同类减法也有这个问题。

复现代码:

int initialized = 7;
int get_value(void) { return initialized; }

链接脚本:

SECTIONS
{
    .text 0x1000 : { *(.text*) }
    .data 0x2000 : { *(.data*) }
    .bss 0x3000 : { *(.bss*) }
}

编译命令:

arm-none-eabi-gcc -marm -mcpu=arm926ej-s -c nonzero.c -o nonzero.o
arm-none-eabi-ld -r -T nonzero.ld nonzero.o -o nonzero.mo

readelf 显示:get_value 的 st_value=0,所属 .text 的 sh_addr=0x1000;initialized 的 st_value=0,所属 .data 的 sh_addr=0x2000。

将生成的 ELF 交给本提交的加载函数和 ARM 重定位函数,在主机上得到:

  • get_value 实际加载在模块起点,导出地址却是 mem_space - 4096,位于分配范围之外。
  • .data 实际加载在 mem_space + 36,initialized 的引用却被写成 mem_space + 36 - 0x2000。
  • 对照基线 9e6dec3b14,同一 ELF 的数据引用为正确的 mem_space + 36。

请按“所属段的实际加载地址 + st_value”计算地址,并一起修正前面的段符号重定位公式。参见 ELF 符号值规则。上述结果来自主机验证,未做 QEMU 或实板验证。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

感谢老师纠正,之前对 ET_REL 的符号值规则理解有误。提交 6880fad 已将已定义符号重定位和函数导出地址统一改为所属节实际加载地址加 st_value,不再减去 sh_addr。非零节地址场景的主机检查及 QEMU 验证均通过。

Convert relocation offsets to module-relative addresses using the target section's load address. Use per-section load addresses for defined symbols and ET_REL exports without subtracting sh_addr. Skip relocations whose target sections were not loaded, such as debug sections.

Verified with 12 host scenarios using ASan/UBSan and seven modules on qemu-vexpress-a9, including COMMON references in data, multiple NOBITS sections, nonzero sh_addr, and -g debug relocations.
@usualtreee

Copy link
Copy Markdown
Author

本轮修复已推送至 6880fad,请各位老师帮忙复审。另外补充处理了带 -g 模块的调试节重定位:跳过目标节未加载的重定位项,避免生成模块外写入地址。

当前 12 组主机回归测试在 ASAN、UBSAN 下全部通过;qemu-vexpress-a9 的 7 个测试模块均通过,包括 COMMON 跨节引用、多个 NOBITS 节、非零节地址和带调试信息模块。尚未做实板及全部架构验证。

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants