SOME/IP 车载窗控仿真环境 glibc 升级与启动脚本修复文档

一、背景

在 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. [1/5] 清理旧进程成功

  2. [2/5] 清理 SHM 成功

  3. [3/5] gatewayd 正常启动

  4. [4/5] someipd 正常启动

  5. [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 是控制器)。修复思路完全一致:

  1. 开头 unset LD_LIBRARY_PATH --- 清除系统级变量,避免覆盖 RUNPATH

  2. 删除 export LD_LIBRARY_PATH --- 避免污染系统命令

  3. 修正 /proc/<pid>/exe --- 让进程清理真正生效

  4. 确认 car_window_controller_bin 已 patchelf --- 让它也能使用 /var/tmp/vsps/lib 里的 glibc

按以上修改后,VM B 脚本应当能正常运行。