| 项 | 内容 |
|---|---|
| 现象 | 在板子上执行httping命令(含空跑)立刻 Segmentation fault |
| 结论 | -pie链接 + 目标文件未-fPIE→ TEXTREL → 启动重定位写只读段 →SEGV_ACCERR |
| 状态 | 已修复并在板端验证通过 |
1. 现象
- 设备上执行
/bin/httping(不带任何参数)即段错误。 - 带 URL 的测速命令同样段错误。
strace显示:依赖库加载成功后,在动态链接阶段崩溃,尚未进入有效业务逻辑。
典型 strace 尾部:
open("/usr/lib/libextservice.so", ...) = 3 mmap2(... PROT_READ|PROT_EXEC ...) = ... mmap2(... PROT_READ|PROT_WRITE ...) = ... mprotect(..., PROT_READ) = 0 # 各库 RELRO mprotect(..., PROT_READ) = 0 mprotect(..., PROT_READ) = 0 --- SIGSEGV {si_code=SEGV_ACCERR, si_addr=0x49xxxx} --- +++ killed by SIGSEGV +++要点:
SEGV_ACCERR:权限错误(典型为写只读页),不是缺页、也不是简单 OOM。si_addr随 ASLR 变化,但相对主程序镜像的偏移固定(落在 RX /.rodata一带)。
2. 根因
2.1 直接原因
| 链接 | 编译 | 结果 |
|---|---|---|
CMakeLDFLAGS含-pie | 几乎所有.o没有-fPIE | PIE + 非 PIC 目标文件 |
ELF 带DT_TEXTREL | 数百个R_ARM_RELATIVE落在RX 只读 LOAD | 启动时写只读段 → 段错误 |
用readelf可见修复前:
Flags: PIE且存在TEXTREL- 指向非可写 LOAD 的 reloc 数量约672
- 各
.o以R_ARM_ABS32为主,几乎没有 PIC 类重定位
2.2 为何会编成这样
- CMake 为安全加固给 httping 加了
-pie,并把-fPIE放进C_CFLAGS。 patch-httping-2.2.1/Makefile里,原先只有在CONFIG_HSAN下、且仅对main.o使用C_CFLAGS。- CMake 调用 make 时未传入
CONFIG_HSAN=y(旧Makefile.hsan会传)。 - 于是所有目标文件走默认规则:只有
CFLAGS/OFLAGS(含头文件路径),没有-fPIE,再与-pie链接。
对比历史可工作路径:Makefile.hsan的LDFLAGS不含-pie,非 PIC 目标文件链成普通可执行文件可以正常跑。CMake 路径引入-pie却未保证全量-fPIE,从而引入本问题。
2.3 与若干误判的关系
| 猜测 | 结论 |
|---|---|
板子仍是旧httping | 排除(md5sum/ 大小与 PC strip 产物一致) |
libextservice.so未更新(曾误用「25764=旧」) | 排除(strip 后本就约 25KB;md5 与 PC 一致) |
系统内存不足 /malloc(128KB)失败 | 排除(MemAvailable 充足;崩溃点在链接收尾) |
interpret_url里char in_use[65536]栈溢出 | 排除(空跑不进该函数;该问题另作加固,栈改堆仍保留) |
3. 定位过程
按时间线概括排查路径:
板端空跑即崩 → md5/大小对照:确认已是新二进制 → free/meminfo:排除内存不足 → 空跑路径分析:排除 interpret_url 大栈 → strace:定位到动态库加载后、SEGV_ACCERR → readelf -d/-r/-l:发现 TEXTREL + 大量 RX 段 reloc → 检查 .o 重定位类型:确认未 -fPIE → 对照 Makefile.hsan / CMakeLists / Makefile:找到 -pie 与 -fPIE 不一致 → 全量 -fPIE 重编:TEXTREL=0,板端恢复正常关键命令(分析侧):
# 动态标志与 TEXTRELreadelf-dhttping|grep-E'TEXTREL|FLAGS|NEEDED'# 程序头:RX / RW LOADreadelf-lhttping# 目标文件是否 PICreadelf-rmain.o tcp.o|grep-cR_ARM_ABS32# 板端strace-fhttping2>&1|tail-40md5sum /bin/httping /usr/lib/libextservice.so4. 修复内容
涉及文件(ubus8.8 / ubus7.20 均已改):
open_source/local/httping/CMakeLists.txt- make 增加
CONFIG_HSAN=y OFLAGS、C_CFLAGS均加入-fPIE- 继续使用
-pie链接
- make 增加
open_source/local/httping/patch-httping-2.2.1/Makefile- 统一规则:
%.o: %.c使用$(CFLAGS) $(C_CFLAGS),保证所有目标文件带-fPIE
- 统一规则:
修复后检查:
DT_TEXTREL消失- 写入非可写 LOAD 的 reloc 数量为0
- 板端:
httping→No URL/host to ping given
5. 重编与验证
说明:必须带prebuild_httping,否则不会重新覆盖补丁目录。本次根因修复只需重编 httping,不必重编libextservice。
产物路径:
local/httping/rootfs/usr/bin/httping板端验证:
md5sum /bin/httping httping# 期望:No URL/host to ping given6. 经验教训
-pie与-fPIE必须成对:只链 PIE、不编 PIC,在 musl 上极易在启动阶段因 TEXTREL 直接 SIGSEGV。- Make 变量与编译宏不是一回事:
CC上的-DCONFIG_HSAN=y只影响 C 预处理;Makefile 的ifdef CONFIG_HSAN需要 make 侧传入CONFIG_HSAN=y。 - strip 后体积不能当版本依据:
libextservice.sostrip 前后可从 ~98KB 变为 ~25KB,应以 md5 / 同源编译产物为准。 - 空跑即崩优先怀疑加载/链接:
strace+readelf比先改业务逻辑更高效。 - 补丁目录中的
interpret_url大栈改堆仍建议保留,属于另一类潜在栈溢出风险,与本次空跑段错误不是同一根因。
7.完整因果链
① 编译时未加-fPIE → 目标文件含绝对地址,代码是"位置相关"的 ↓ ② 链接时加了-pie → 链接器要求生成"位置无关"可执行文件 → 矛盾出现 ↓ ③ 链接器的选择 ├─ 现代链接器:直接报错"recompile with -fPIE"└─ 老链接器:妥协,生成 TEXTREL → 在.text 段留下重定位项 ↓ ④ 程序启动,ld.so 处理重定位 → 重定位项指向.text → 必须修正.text 里写死的绝对地址 ↓ ⑤ ld.so 写.text →.text 权限是 R-X(只读+可执行) → 写入触发 page fault ↓ ⑥ 内核 page fault handler 判定 → 地址有效(已映射) → 权限不对(只读页要写) → 发送 SIGSEGV,si_code=SEGV_ACCERR ↓ ⑦ 程序崩溃8. 总结
根本原因:编译时没加 -fPIE,链接时却加了 -pie,链接器被迫在只读的 .text 段留下重定位项(TEXTREL);启动时动态链接器要改写 .text,但 .text 只读,写入触发 page fault,内核判定权限错误,发送 SIGSEGV,si_code = SEGV_ACCERR。
核心理解:
.text 不是绝对地址,它是代码段;问题是 .text 里的代码写了绝对地址
重定位要修正这些写死的地址,修正位置就在 .text 里
.text 只读,一写就段错误
正常 PIE 把绝对地址放到可写的 .got 里,重定位只改 .got
解决办法:编译和链接选项配套,要么全 -fPIE -pie,要么全 -no-pie。