一、背景
在 Forlinx(AArch64)目标板上运行 SOME/IP 车载窗控仿真程序(car_window_bin、gatewayd、someipd)时,程序依赖的 libvsomeip3.so.3 需要 GLIBC 2.30 和 GLIBC 2.28,而目标板系统自带的 glibc 版本过低,导致程序启动时报错:

./someipd: /lib/aarch64-linux-gnu/libpthread.so.0: version `GLIBC_2.30' not found
./someipd: /lib/aarch64-linux-gnu/libc.so.6: version `GLIBC_2.28' not found
为了不修改系统原有 glibc ,我们在 /var/tmp/vsps/lib 目录下放置了一套独立的 glibc 2.35,并让目标程序只使用这套 glibc ,而系统命令(ls、cat、sleep 等)仍然使用系统自带 glibc。
二、问题分析过程(踩坑记录)
坑 1:只拷贝 libc.so.6 是不够的
glibc 不是单个文件,而是由 ld-linux-aarch64.so.1(动态链接器)和 libc.so.6、libpthread.so.0、libm.so.6 等 200 多个共享库组成。动态链接器和 libc 必须来自同一套 glibc,版本完全一致。
如果只替换 libc.so.6,程序仍使用系统默认的旧版 ld-linux,会报:
symbol lookup error: /var/tmp/vsps/lib/libc.so.6: undefined symbol: _dl_fatal_printf, version GLIBC_PRIVATE
坑 2:export LD_LIBRARY_PATH 会污染整个 shell 环境
在启动脚本开头 **export LD_LIBRARY_PATH=/var/tmp/vsps/lib**会导致:
-
脚本里所有系统命令(
sleep、cat、grep、kill)也去/var/tmp/vsps/lib找libc.so.6 -
系统命令使用系统默认的
ld-linux,但加载了高版本libc.so.6,同样报_dl_fatal_printf -
结果整个脚本到处报错,看起来像
someipd崩溃,实际上是sleep/cat崩溃
坑 3:LD_LIBRARY_PATH 优先级高于 RUNPATH
即使使用 patchelf 把 RUNPATH 写进了二进制,只要 shell 环境里存在 LD_LIBRARY_PATH,动态链接器仍会优先使用 LD_LIBRARY_PATH 里的路径,RUNPATH 被忽略。
通过 LD_DEBUG=libs 可以看到:
search path=/usr/local/lib:/lib/aarch64-linux-gnu (LD_LIBRARY_PATH)
trying file=/lib/aarch64-linux-gnu/libc.so.6
程序加载了系统旧版 libc.so.6,与 /var/tmp/vsps/lib 里的 2.35 版 ld-linux 不匹配,报:
Inconsistency detected by ld.so: dl-call-libc-early-init.c: 37:
_dl_call_libc_early_init: Assertion `sym != NULL' failed!
坑 4:ld-linux-aarch64.so.1 缺少可执行权限
patchelf 修改二进制后,如果 ld-linux-aarch64.so.1 本身没有 x 权限,内核在 execve 时会直接拒绝,报:
./car_window_bin: Permission denied
坑 5:patchelf 未成功写入 RUNPATH
在开发机(Windows/Ubuntu)上执行 patchelf 修改的是开发机上的文件,对目标板无效。必须在目标板上 对目标文件执行 patchelf。
三、最终解决方案总览
| 步骤 | 操作 | 目的 |
|---|---|---|
| 1 | 拷贝完整 glibc 2.35 到 /var/tmp/vsps/lib |
提供高版本 glibc |
| 2 | 给 ld-linux-aarch64.so.1 加 x 权限 |
内核能加载动态链接器 |
| 3 | 用 patchelf 修改三个程序的解释器和 RUNPATH |
让程序自动使用指定 glibc |
| 4 | 启动脚本开头 unset LD_LIBRARY_PATH |
避免环境变量覆盖 RUNPATH |
| 5 | 启动脚本中不再 export LD_LIBRARY_PATH |
避免污染系统命令 |
四、详细操作步骤
步骤 1:准备完整 glibc 2.35
从与目标板架构一致的 glibc 2.35 发行包(如 Ubuntu 22.04 的 libc6 包,或交叉编译工具链 sysroot)中提取以下文件,全部放到 /var/tmp/vsps/lib/:
ld-linux-aarch64.so.1 libc.so.6 libpthread.so.0 libm.so.6 libdl.so.2 librt.so.1 libresolv.so.2 libnsl.so.1 libutil.so.1 libanl.so.1 libBrokenLocale.so.1 ...(以及所有 glibc 相关 .so)
验证版本一致性:
cd /var/tmp/vsps/lib ./ld-linux-aarch64.so.1 --version | head -1 strings libc.so.6 | grep 'GNU C Library' | head -1
两者都应输出 stable release version 2.35。
步骤 2:给动态链接器加可执行权限
chmod +x /var/tmp/vsps/lib/ld-linux-aarch64.so.1 ls -l /var/tmp/vsps/lib/ld-linux-aarch64.so.1
输出应包含 x 位,如 -rwxr-xr-x。
原因 :内核在 execve 时,会检查 INTERP 指定的动态链接器是否有可执行权限。没有 x 位会直接报 Permission denied。
步骤 3:用 patchelf 修改目标程序
对 someipd、gatewayd、car_window_bin 三个程序分别执行:
bash
cd /var/tmp/vsps
for prog in someipd gatewayd car_window_bin; do
echo "=== Processing $prog ==="
cp -f "$prog" "$prog.bak" # 备份
chmod 755 "$prog" # 确保可写可执行
patchelf --set-interpreter /var/tmp/vsps/lib/ld-linux-aarch64.so.1 "$prog"
patchelf --set-rpath /var/tmp/vsps/lib "$prog"
done
验证:
bash
for prog in someipd gatewayd car_window_bin; do
echo "=== $prog ==="
readelf -l "$prog" | grep interpreter
readelf -d "$prog" | grep -E 'RPATH|RUNPATH'
done
每个程序都应输出:
[Requesting program interpreter: /var/tmp/vsps/lib/ld-linux-aarch64.so.1]
(RUNPATH) Library runpath: [/var/tmp/vsps/lib]
作用:
-
--set-interpreter:让内核启动程序时使用/var/tmp/vsps/lib/ld-linux-aarch64.so.1作为动态链接器 -
--set-rpath:让动态链接器优先从/var/tmp/vsps/lib加载依赖库
步骤 4:修改启动脚本 start_vm_a.sh
4.1 删除全局 export LD_LIBRARY_PATH
删除这一行:
export LD_LIBRARY_PATH="$REMOTE_DIR/lib"
原因 :export 会让整个脚本(包括 sleep、cat、grep、kill 等系统命令)都去 /var/tmp/vsps/lib 找 libc.so.6,导致系统命令崩溃。
4.2 在脚本开头添加 unset LD_LIBRARY_PATH
在 #!/bin/sh 之后添加:
unset LD_LIBRARY_PATH
原因 :目标板系统级或用户级配置里可能设置了
LD_LIBRARY_PATH(例如/usr/local/lib:/lib/aarch64-linux-gnu)。该变量优先级高于二进制里的
RUNPATH,会导致程序加载系统旧版libc.so.6,与/var/tmp/vsps/lib里的 2.35 版ld-linux不匹配,触发_dl_call_libc_early_init断言失败。
4.3 修正进程清理循环
原脚本:
for p in /proc/*; do
if [ -f "$p/exefile" ]; then
问题:
/proc/<pid>/exefile不是标准路径,标准是/proc/<pid>/exe
/proc/<pid>/exe是符号链接,-f测试会失败
修正为:
for p in /proc/[0-9]*; do if [ -L "$p/exe" ]; then if grep -q -E "someipd|gatewayd|car_window" "$p/exe" 2>/dev/null; then kill -9 "${p##*/}" 2>/dev/null fi fi done
五、最终启动脚本
bash
#!/bin/sh
# 清除可能存在的 LD_LIBRARY_PATH,避免覆盖二进制里的 RUNPATH
unset LD_LIBRARY_PATH
REMOTE_DIR="/var/tmp/vsps"
export VSOMEIP_CONFIGURATION="$REMOTE_DIR/config/vsomeip_vm_a.json"
echo "=================================================================="
echo " Window Simulator Startup"
echo "=================================================================="
echo ""
echo "[1/5] Stopping all old processes..."
for p in /proc/[0-9]*; do
if [ -L "$p/exe" ]; then
if grep -q -E "someipd|gatewayd|car_window" "$p/exe" 2>/dev/null; then
kill -9 "${p##*/}" 2>/dev/null
fi
fi
done
sleep 2
echo " Done."
echo ""
echo "[2/5] Cleaning all shared memory..."
rm -f /var/vsomeip* 2>/dev/null && echo " /var/vsomeip* cleaned" || true
rm -f /var/vsomeip.lck 2>/dev/null && echo " /var/vsomeip.lck cleaned" || true
rm -f /dev/shmem/vsomeip* 2>/dev/null && echo " /dev/shmem/vsomeip* cleaned" || true
rm -f /dev/shmem/lola-* 2>/dev/null && echo " /dev/shmem/lola-* cleaned" || true
rm -rf /tmp_discovery/* 2>/dev/null && echo " /tmp_discovery/* cleaned" || true
echo " Done."
echo ""
echo "[3/5] Starting car_window_bin FIRST..."
cd "$REMOTE_DIR" || exit
./car_window_bin \
--service_instance_manifest config/mw_com_config.json \
2>app.log &
APP_PID=$!
echo " App started (PID $APP_PID)"
sleep 5
if kill -0 $APP_PID 2>/dev/null; then
echo " App is running ✅"
else
echo " ERROR: App crashed!"
cat "$REMOTE_DIR/app.log"
exit 1
fi
echo " SHM status:"
ls -la /dev/shmem/lola-* 2>/dev/null || echo " WARNING: No SHM found!"
echo ""
echo "[4/5] Starting gatewayd..."
cd "$REMOTE_DIR" || exit
./gatewayd \
--configuration config/gatewayd/mw_someip_config_vm_a.bin \
--service_instance_manifest config/gatewayd/mw_com_config.json \
> gatewayd.log 2>&1 &
GATEWAYD_PID=$!
echo " gatewayd started (PID $GATEWAYD_PID)"
sleep 3
if kill -0 $GATEWAYD_PID 2>/dev/null; then
echo " gatewayd is running ✅"
else
echo " ERROR: gatewayd crashed!"
cat "$REMOTE_DIR/gatewayd.log"
exit 1
fi
echo ""
echo " gatewayd.log:"
cat "$REMOTE_DIR/gatewayd.log"
echo ""
echo "[5/5] Starting someipd..."
route add -net 224.0.0.0/4 -interface eth0 2>/dev/null || true
cd "$REMOTE_DIR"
./someipd \
--configuration config/someipd/mw_someip_config_vm_a.bin \
--service_instance_manifest config/someipd/mw_com_config.json \
> someipd.log 2>&1 &
SOMEIPD_PID=$!
echo " someipd started (PID $SOMEIPD_PID)"
sleep 3
if kill -0 $SOMEIPD_PID 2>/dev/null; then
echo " someipd is running ✅"
else
echo " ERROR: someipd crashed!"
cat "$REMOTE_DIR/someipd.log"
exit 1
fi
echo ""
echo "=================================================================="
echo " Services running:"
echo " App PID: $APP_PID"
echo " gatewayd PID: $GATEWAYD_PID"
echo " someipd PID: $SOMEIPD_PID"
echo ""
echo " Attaching to app output..."
echo "=================================================================="
wait $APP_PID
六、验证步骤
6.1 验证二进制修改成功
cd /var/tmp/vsps
for prog in someipd gatewayd car_window_bin; do
echo "=== $prog ==="
readelf -l "$prog" | grep interpreter
readelf -d "$prog" | grep -E 'RPATH|RUNPATH'
done
6.2 验证动态链接器可执行
ls -l /var/tmp/vsps/lib/ld-linux-aarch64.so.1
必须有 x 位。
6.3 验证 glibc 版本一致
cd /var/tmp/vsps/lib
./ld-linux-aarch64.so.1 --version | head -1
strings libc.so.6 | grep 'GNU C Library' | head -1
两者都是 2.35 才配套。
6.4 单独测试每个程序
cd /var/tmp/vsps
unset LD_LIBRARY_PATH
./car_window_bin --service_instance_manifest config/mw_com_config.json
如果启动后正常输出 SOME/IP 日志,说明成功。
6.5 运行完整脚本
cd /var/tmp/vsps
./start_vm_a.sh
预期:
-
[3/5]car_window_bin 正常运行,显示WindowInfo offered to gatewayd -
[4/5]gatewayd 正常运行,日志里能看到 SOME/IP 服务发现 -
[5/5]someipd 正常运行 -
最后停在
wait $APP_PID
七、常见问题排查
问题 1:_dl_fatal_printf undefined symbol
原因 :ld-linux 和 libc.so.6 版本不匹配,或者 LD_LIBRARY_PATH 指向了错误的 libc.so.6。
排查:
echo "$LD_LIBRARY_PATH"
cd /var/tmp/vsps/lib
./ld-linux-aarch64.so.1 --version
strings libc.so.6 | grep 'GNU C Library'
问题 2:_dl_call_libc_early_init Assertion `sym != NULL' failed
原因 :LD_LIBRARY_PATH 覆盖了 RUNPATH,程序加载了系统旧版 libc.so.6。
排查:
LD_DEBUG=libs ./car_window_bin ... 2>&1 | grep 'search path'
如果看到 (LD_LIBRARY_PATH) 而路径里没有 /var/tmp/vsps/lib,就是这个问题。
解决 :unset LD_LIBRARY_PATH 或在脚本开头 unset。
问题 3:Permission denied
原因:
-
ld-linux-aarch64.so.1没有x位 -
目标程序没有
x位 -
文件系统挂载了
noexec
排查:
ls -l /var/tmp/vsps/lib/ld-linux-aarch64.so.1
ls -l /var/tmp/vsps/car_window_bin
mount | grep -E 'noexec'
问题 4:version GLIBC_2.XX not found
原因 :某个依赖库(如 libvsomeip3.so.3)仍然从系统路径加载了旧版 glibc。
排查:
LD_DEBUG=libs ./car_window_bin ... 2>&1 | grep 'trying file'
解决 :把 /var/tmp/vsps/lib 加到 RUNPATH,或对该库也做 patchelf 处理。
八、总结
| 核心要点 | 说明 |
|---|---|
| glibc 是一整套 | 不能只替换 libc.so.6,必须整套替换 |
ld-linux 和 libc 必须同版本 |
否则会报 _dl_fatal_printf 或 _dl_call_libc_early_init |
用 patchelf 而不是 LD_LIBRARY_PATH |
RUNPATH 写进二进制更稳定,不影响系统命令 |
LD_LIBRARY_PATH 优先级高于 RUNPATH |
必须 unset,否则 RUNPATH 失效 |
ld-linux 必须有 x 位 |
否则内核拒绝执行 |
patchelf 必须在目标板上执行 |
开发机上的修改不影响目标板 |
按照以上步骤操作后,car_window_bin、gatewayd、someipd 均可在不改变系统 glibc 的前提下正常运行。

start_vm_b.sh 修复说明与完整脚本
一、这个脚本存在的问题
和你之前遇到的完全相同,共有 3 处需要修复:
| # | 问题 | 位置 | 后果 |
|---|---|---|---|
| 1 | export LD_LIBRARY_PATH="$REMOTE_DIR/lib" 污染整个 shell |
第 4 行 | sleep、cat、grep、kill 等系统命令全部报 _dl_fatal_printf |
| 2 | 缺少 unset LD_LIBRARY_PATH,无法清除系统残留的同名变量 |
脚本开头 | 即使删掉 export,系统级 LD_LIBRARY_PATH 仍会覆盖 RUNPATH,导致 _dl_call_libc_early_init 断言失败 |
| 3 | /proc/*/exefile 路径错误 |
[1/5] 循环 |
进程清理不生效,旧进程残留会导致 SHM 冲突 |
注意 :
car_window_controller_bin也是需要patchelf处理的目标程序之一(它依赖libvsomeip3.so.3),需要一并确认它的解释器和 RUNPATH 已正确设置。
bash
#!/bin/sh
# 清除可能存在的 LD_LIBRARY_PATH,避免覆盖二进制里的 RUNPATH
unset LD_LIBRARY_PATH
REMOTE_DIR="/var/tmp/vsps"
export VSOMEIP_CONFIGURATION="$REMOTE_DIR/config/vsomeip_vm_b.json"
echo "=================================================================="
echo " Window Controller Startup"
echo "=================================================================="
echo ""
echo "[1/5] Stopping all old processes..."
for p in /proc/[0-9]*; do
if [ -L "$p/exe" ]; then
if grep -q -E "someipd|gatewayd|car_window" "$p/exe" 2>/dev/null; then
kill -9 "${p##*/}" 2>/dev/null
fi
fi
done
sleep 2
echo " Done."
echo ""
echo "[2/5] Cleaning all shared memory..."
rm -f /var/vsomeip* 2>/dev/null && echo " /var/vsomeip* cleaned" || true
rm -f /var/vsomeip.lck 2>/dev/null && echo " /var/vsomeip.lck cleaned" || true
rm -f /dev/shmem/vsomeip* 2>/dev/null && echo " /dev/shmem/vsomeip* cleaned" || true
rm -f /dev/shmem/lola-* 2>/dev/null && echo " /dev/shmem/lola-* cleaned" || true
rm -rf /tmp_discovery/* 2>/dev/null && echo " /tmp_discovery/* cleaned" || true
echo " Done."
echo ""
echo "[3/5] Starting gatewayd..."
cd "$REMOTE_DIR" || exit
./gatewayd \
--configuration config/gatewayd/mw_someip_config_vm_b.bin \
--service_instance_manifest config/gatewayd/mw_com_config.json \
> gatewayd.log 2>&1 &
GATEWAYD_PID=$!
echo " gatewayd started (PID $GATEWAYD_PID)"
sleep 3
if kill -0 $GATEWAYD_PID 2>/dev/null; then
echo " gatewayd is running ✅"
else
echo " ERROR: gatewayd crashed!"
cat "$REMOTE_DIR/gatewayd.log"
exit 1
fi
echo ""
echo "[4/5] Starting someipd..."
route add -net 224.0.0.0/4 -interface eth0 2>/dev/null || true
cd "$REMOTE_DIR" || exit
./someipd \
--configuration config/someipd/mw_someip_config_vm_b.bin \
--service_instance_manifest config/someipd/mw_com_config.json \
> someipd.log 2>&1 &
SOMEIPD_PID=$!
echo " someipd started (PID $SOMEIPD_PID)"
sleep 3
if kill -0 $SOMEIPD_PID 2>/dev/null; then
echo " someipd is running ✅"
else
echo " ERROR: someipd crashed!"
cat "$REMOTE_DIR/someipd.log"
exit 1
fi
echo ""
echo "=================================================================="
echo " Services running:"
echo " gatewayd PID: $GATEWAYD_PID"
echo " someipd PID: $SOMEIPD_PID"
echo ""
echo "[5/5] Starting car_window_controller_bin (interactive)..."
echo " Controls: [O]pen [C]lose [S]top [E]xit"
echo "=================================================================="
cd "$REMOTE_DIR" || exit
exec ./car_window_controller_bin \
--service_instance_manifest config/mw_com_config.json
三、修改点对比
| 行 | 原脚本 | 修改后 | 原因 |
|---|---|---|---|
| 3 | (无) | unset LD_LIBRARY_PATH |
清除系统级残留,避免覆盖 RUNPATH |
| 4 | export LD_LIBRARY_PATH="$REMOTE_DIR/lib" |
删除 | 避免污染 shell,让系统命令用系统 glibc |
| 12 | for p in /proc/*; do |
for p in /proc/[0-9]*; do |
只遍历数字 PID 目录 |
| 13 | if [ -f "$p/exefile" ]; then |
if [ -L "$p/exe" ]; then |
/proc/<pid>/exe 是符号链接,-L 正确 |
| 14 | grep ... "$p/exefile" |
grep ... "$p/exe" |
同上 |
| 40 | cd "$REMOTE_DIR" |
`cd "$REMOTE_DIR" | |
| 72 | cd "$REMOTE_DIR" |
`cd "$REMOTE_DIR" | |
| 90 | cd "$REMOTE_DIR" |
`cd "$REMOTE_DIR" |
四、使用前必须先确认 car_window_controller_bin 已 patchelf
car_window_controller_bin 是这个脚本第 [5/5] 步启动的程序,也是依赖 libvsomeip3.so.3 的可执行文件,必须和 someipd、gatewayd 一样做 patchelf 处理。
4.1 检查是否已处理
sh
cd /var/tmp/vsps
readelf -l car_window_controller_bin | grep interpreter
readelf -d car_window_controller_bin | grep -E 'RPATH|RUNPATH'
期望输出:
text
[Requesting program interpreter: /var/tmp/vsps/lib/ld-linux-aarch64.so.1]
(RUNPATH) Library runpath: [/var/tmp/vsps/lib]
4.2 如果未处理,执行 patchelf
sh
cd /var/tmp/vsps
cp -f car_window_controller_bin car_window_controller_bin.bak
chmod 755 car_window_controller_bin
patchelf --set-interpreter /var/tmp/vsps/lib/ld-linux-aarch64.so.1 car_window_controller_bin
patchelf --set-rpath /var/tmp/vsps/lib car_window_controller_bin
4.3 再次验证
sh
readelf -l car_window_controller_bin | grep interpreter
readelf -d car_window_controller_bin | grep -E 'RPATH|RUNPATH'
五、执行脚本
sh
cd /var/tmp/vsps
chmod +x start_vm_b.sh car_window_controller_bin gatewayd someipd
./start_vm_b.sh
预期行为:
-
[1/5]清理旧进程成功 -
[2/5]清理 SHM 成功 -
[3/5]gatewayd 正常启动 -
[4/5]someipd 正常启动 -
[5/5]进入交互模式,显示Controls: [O]pen [C]lose [S]top [E]xit
六、常见问题
Q1:someipd crashed! 但日志为空
说明 someipd 在启动时就被内核拒绝。检查:
sh
ls -l /var/tmp/vsps/lib/ld-linux-aarch64.so.1
如果缺少 x 位:chmod +x /var/tmp/vsps/lib/ld-linux-aarch64.so.1
Q2:_dl_fatal_printf 又出现了
说明 LD_LIBRARY_PATH 没被清干净。检查:
sh
echo "$LD_LIBRARY_PATH"
grep -rn LD_LIBRARY_PATH /etc/profile /etc/profile.d/ /etc/environment ~/.bashrc 2>/dev/null
找到后编辑对应文件,注释掉设置 LD_LIBRARY_PATH 的行。
Q3:car_window_controller_bin 报 Permission denied
sh
ls -l /var/tmp/vsps/car_window_controller_bin
chmod 755 /var/tmp/vsps/car_window_controller_bin
同时确认 ld-linux-aarch64.so.1 有 x 位。
Q4:_dl_call_libc_early_init Assertion 失败
说明 shell 里还有 LD_LIBRARY_PATH。执行:
sh
unset LD_LIBRARY_PATH
./start_vm_b.sh
或者在脚本开头保留 unset LD_LIBRARY_PATH(已包含在修改后的脚本里)。
七、小结
start_vm_b.sh 与 start_vm_a.sh 是姊妹脚本,VM B 端多了 car_window_controller_bin(交互式控制器),少了一个 car_window_bin(VM A 是窗口模拟器,VM B 是控制器)。修复思路完全一致:
-
开头
unset LD_LIBRARY_PATH--- 清除系统级变量,避免覆盖 RUNPATH -
删除
export LD_LIBRARY_PATH--- 避免污染系统命令 -
修正
/proc/<pid>/exe--- 让进程清理真正生效 -
确认
car_window_controller_bin已 patchelf --- 让它也能使用/var/tmp/vsps/lib里的 glibc
按以上修改后,VM B 脚本应当能正常运行。