首页 一种让 Linux 上的 C/C++ 程序自带依赖库的打包方式
文章
取消

一种让 Linux 上的 C/C++ 程序自带依赖库的打包方式

Linux 上的 C/C++ 程序编译打包后拿到 同 CPU 架构 的其他 Linux 系统上运行时,通常会由于目标系统上缺少该程序所需的库而无法运行。

这个问题在一个连接上互联网的 Linux 系统上可以很容易的通过 yumapt 安装缺少的依赖库来解决,但是如果是在一个无法使用 yumapt 的内网系统上,则非常麻烦。

本文介绍一种打包方式,让打包后的程序自带依赖库,即使在目标系统上没有该程序所需的库时,也能正常运行。

步骤

以下为该方式的详细步骤:

第一步:编译平台的选择

选择一个 libc 版本 小于等于 该程序需要适配的所有系统中 libc 版本最小的系统。

比如该程序需要适配 2 个系统,这两个系统的 libc 版本分别为 2.232.28,则选择一个 libc 版本 小于等于 2.23 的系统作为编译平台。

第二步:拷贝依赖库

在编译完成后,打包之前,通过 ldd 命令查询该程序中所有 ELF 文件的依赖库,并将查询到的 除 libc 以外的所有依赖库拷贝到该程序的安装目录中(通常是安装目录下的 lib 目录),然后再进行打包。

如果打包方式为 rpm,建议在 spec 文件中添加 AutoReqProv: no 选项。

第三步(可选):添加 RPATH

在打包之前,给所有该程序中的 ELF 文件添加 RPATH 指向其自带的 lib 目录,让程序在运行时自动优先查找自己的 lib 目录中的库文件,这样程序运行时就不需要目标系统上安装有其依赖库了。

添加 RPATH 需要用到另外一个工具 patchelf,可点击链接下载后进行编译安装。

当然也可以不添加 RPATH,而是在使用时配置 LD_LIBRARY_PATH 环境变量指向其自带的 lib 目录达到同样的效果。

示例

下面以 postgresql 为例对该打包方式进行演示:

第一步:编译平台的选择

选择 CentOS 6 作为编译平台,其 libc 版本为 2.12

1
2
3
4
5
6
7
8
9
10
11
12
[root@el6-x86_64 ~]# ls -l /lib64/libc.so.6
lrwxrwxrwx 1 root root 12 Jul  8 13:13 /lib64/libc.so.6 -> libc-2.12.so
[root@el6-x86_64 ~]# tar xf postgresql-9.6.24.tar.gz
[root@el6-x86_64 ~]# cd postgresql-9.6.24/
[root@el6-x86_64 postgresql-9.6.24]# ./configure --prefix=/usr/local/pg9
...
[root@el6-x86_64 postgresql-9.6.24]# make -j`nproc` && make install
...
[root@el6-x86_64 postgresql-9.6.24]# cd /usr/local/pg9
[root@el6-x86_64 pg9]# ls
bin include lib share
[root@el6-x86_64 pg9]# 

第二步:拷贝依赖库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@el6-x86_64 ~]# ./copy_deps.sh /usr/local/pg9/ /usr/local/pg9/lib
...
Processing /lib64/libc.so.6
Processing /lib64/libdl.so.2 
Processing /lib64/1ibm.so.6
Processing /lib64/libpthread.so.0 
Processing /lib64/libreadline.so.6
`/lib64/libreadline.so.6' -> `/usr/local/pg9/1ib/libreadline.so.6' 
Processing /lib64/1ibrt.so.1
Processing/lib64/libtinfo.so.5
`/lib64/libtinfo.so.5' -> `/usr/local/pg9/1ib/libtinfo.so.5' 
Processing /lib64/1ibz.so.1
`/lib64/libz.so.1' -> `/usr/local/pg9/1ib/libz.so.1' 
Processing /usr/local/pg9/lib/libecpg.so.6
Processing /usr/local/pg9/1ib/libpgtypes.so.3 
Processing /usr/local/pg9/lib/libpq.so.5 
[root@el6-x86_64 ~]# 

以下为 copy_deps.sh 脚本内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#/bin/bash -e
# Copy the deps of all elf files in `ELFDIR` to `LIBDIR`.

PROGNAME=$(basename $0)
if [[ $# != 2 ]]; then
    echo "Usage: $PROGNAME ELFDIR LIBDIR" >&2
    exit 1
fi

ELFDIR=$1
LIBDIR=$2

for elf in `find $ELFDIR -type f -exec file {} + | grep ELF | cut -d: -f1`; do 
    echo "Analysing $elf"
    ldd $elf
    for sopath in `ldd $elf | grep -E '.+.so.* => /.+.so.* \(θx.+\)' | awk '{print $3}'`; do 
        sopaths+=($sopath)
    done
done

sopaths=(`for i in ${sopaths[*]}; do echo $i; done | sort -u`) 

for sopath in ${sopaths[*]}; do
    echo "Processing $sopath" 
    soname=`basename $sopath`
    if [[ (! -e $LIBDIR/$soname) ]]; then
        if [[ `which dpkg 2> /dev/null` ]]; then
            owninfo=`dpkg -S $sopath 2> /dev/null ||:`
        else
            owninfo=`rpm -qf $sopath 2> /dev/null ||:`
        fi
        if [[ ! $owninfo =~ ^glibc|^libc6 ]]; then 
            cp -v $sopath $LIBDIR
        fi
    fi
done

第三步(可选):添加 RPATH

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@el6-x86_64 ~]# ./set_relative_rpath.sh /usr/local/pg9/ /usr/local/pg9/lib
Set the rpath of /usr/local/pg9/lib/libz.so.1 to $ORIGIN
Set the rpath of /usr/local/pg9/1ib/libecpg_compat.so.3.8 to $ORIGIN
Set the rpath of /usr/local/pg9/1ib/libreadline.so.6 to $ORIGIN
Set the rpath of /usr/local/pg9/1ib/libtinfo.so.5 to $ORIGIN
Set the rpath of /usr/local/pg9/1ib/libecpg.so.6.8 to $ORIGIN
Set the rpath of/usr/local/pg9/1ib/postgresq1/utf8_and_uhc.so to $ORIGIN/..
Set the rpath of/usr/local/pg9/1ib/postgresq1/latin2_and_win1250.so to $ORIGIN/..
...
Set the rpath of/usr/local/pg9/bin/clusterdb to $ORIGIN/../lib
Set the rpath of /usr/local/pg9/bin/postgres to $ORIGIN/../1ib
Set the rpath of/usr/local/pg9/bin/pg_restore to $ORIGIN/../1ib
Set the rpath of/usr/local/pg9/bin/ecpg to $ORIGIN/../lib 
[root@el6-x86_64 ~]# 

以下为 set_relative_rpath.sh 脚本内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#/bin/bash -e
# Set the rpath of all elf files in `ELFDIR` to relative paths to `LIBDIR`.

PROGNAME=$(basename $0)
if [[ $# != 2 ]]; then
    echo "Usage: $PROGNAME ELFDIR LIBDIR" >&2
    exit 1
fi

ELFDIR=$1
LIBDIR=$2

for elf in $(find $ELFDIR -type f -exec file {} + | grep ELF | cut -d: -f1); do
    elf_parentdir=$(dirname $elf)
    relative_path=$(realpath --relative-to=$elf_parentdir $LIBDIR)
    if [[ $relative_path == '.' ]]; then
        relative_rpath="\$ORIGIN"
    else
        relative_rpath="\$ORIGIN/$relative_path"
    fi
    if [[ $(patchelf --print-rpath $elf) != $relative_rpath ]]; then
        echo "Set the rpath of $elf to $relative_rpath"
        patchelf --set-rpath $relative_rpath $elf
    fi  
done

第四步:验证

Centos 6 上的 postgresql 安装目录打包:

1
2
3
4
5
[root@el6-x86_64 ~]# cd /usr/local/
[root@el6-x86_64 local]# tar cf pg9.tar ./pg9/
[root@el6-x86_64 local]# ls
bin doc etc games include lib lib64 libexec patchelf pg9 pg9.tar pgsql sbin share src ssl
[root@el6-x86_64 local]#

将打包的 postgresql 放到另外一个 Ubuntu 20 系统上,该系统 libc 版本为 2.31,验证程序是否可正常使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
root@ubt20-x86-64:~# ls -l /lib/x86_64-linux-gnu/libc.so.6
lrwxrwxrwx 1 root root 12 Dec 16 2020 /lib/x86_64-linux-gnu/libc.so.6 -> libc-2.31.so
root@ubt20-x86-64:~# tar xf pg9.tar
root@ubt20-x86-64:~# cd pg9/
root@ubt20-x86-64:~/pg9# ldd ./bin/psql
        linux-vdso.so.1 (0x00097ffe0ace7000)
        libpq.so.5 => /root/pg9/./bin/../lib/libpq.so.5 (0x00007f374e68a000)
        libreadline.so.6 => /root/pg9/./bin/../lib/libreadline.so.6 (0x0000003fd9200000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f374e52f000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f374e33d090)
        libpthread.so.Q => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f374e31a000)
        libtinfo.so.5 => /root/pg9/./bin/../lib/libtinfo.so.5 (0x0000603fdaa00000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f374e8b5000)
root@ubt20-x86-64:~/pg9# ./bin/psql -V
psql (PostgreSQL) 9.6.24
root@ubt20-x86-64:~/pg9# 
本文由作者按照 CC BY 4.0 进行授权

Python 入门 - 12 - 模块和包

国产操作系统和 CPU 分类