查看可执行程序(ELF)或动态库所依赖动态库——ldd脚本分析

本文介绍如何使用ldd和readelf命令检查Linux程序及其所需的动态库依赖。这些工具可以帮助开发者了解程序运行所需的具体库文件。

1.序

搞清可执行程序(动态库)所依赖的动态库信息是非常有帮助的。

2.查看方法

系统信息:

Linux netview 4.4.0-101-generic #124~14.04.1-Ubuntu SMP Fri Nov 10 19:06:11 UTC 2017 i686 i686 i686 GNU/Linux

2.1 使用 ldd

# ldd `which gdb`

linux-gate.so.1 =>  (0xb7737000)
libreadline.so.6 => /usr/local/lib/libreadline.so.6 (0xb76fa000)
libdl.so.2 => /lib/i386-linux-gnu/libdl.so.2 (0xb76da000)
libncurses.so.5 => /lib/i386-linux-gnu/libncurses.so.5 (0xb76b4000)
libtinfo.so.5 => /lib/i386-linux-gnu/libtinfo.so.5 (0xb7692000)
libz.so.1 => /lib/i386-linux-gnu/libz.so.1 (0xb7678000)
libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb7632000)
libpython3.4m.so.1.0 => /usr/lib/i386-linux-gnu/libpython3.4m.so.1.0 (0xb7251000)
libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xb7234000)
libexpat.so.1 => /lib/i386-linux-gnu/libexpat.so.1 (0xb720b000)
liblzma.so.5 => /lib/i386-linux-gnu/liblzma.so.5 (0xb71e5000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7034000)
/lib/ld-linux.so.2 (0xb7739000)
libutil.so.1 => /lib/i386-linux-gnu/libutil.so.1 (0xb7030000)
# ldd `gcc -print-file-name=libreadline.so.6`
linux-gate.so.1 =>  (0xb77cf000)
libtinfo.so.5 => /lib/i386-linux-gnu/libtinfo.so.5 (0xb7750000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb759f000)
/lib/ld-linux.so.2 (0xb77d1000)
ldd `gcc -print-file-name=libpthread.so.0` 
linux-gate.so.1 =>  (0xb772a000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb753e000)
/lib/ld-linux.so.2 (0xb772c000)

2.2 使用 readelf -d

# readelf -d `which gdb`

Dynamic section at offset 0x4d2ec4 contains 34 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libreadline.so.6]
 0x00000001 (NEEDED)                     Shared library: [libdl.so.2]
 0x00000001 (NEEDED)                     Shared library: [libncurses.so.5]
 0x00000001 (NEEDED)                     Shared library: [libtinfo.so.5]
 0x00000001 (NEEDED)                     Shared library: [libz.so.1]
 0x00000001 (NEEDED)                     Shared library: [libm.so.6]
 0x00000001 (NEEDED)                     Shared library: [libpython3.4m.so.1.0]
 0x00000001 (NEEDED)                     Shared library: [libpthread.so.0]
 0x00000001 (NEEDED)                     Shared library: [libexpat.so.1]
 0x00000001 (NEEDED)                     Shared library: [liblzma.so.5]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x0000000c (INIT)                       0x8089c24
 0x0000000d (FINI)                       0x83640bc
 0x00000019 (INIT_ARRAY)                 0x851beb8
 0x0000001b (INIT_ARRAYSZ)               4 (bytes)
 0x0000001a (FINI_ARRAY)                 0x851bebc
 0x0000001c (FINI_ARRAYSZ)               4 (bytes)
 0x6ffffef5 (GNU_HASH)                   0x80481ac
 0x00000005 (STRTAB)                     0x80685a4
 0x00000006 (SYMTAB)                     0x80509d4
 0x0000000a (STRSZ)                      120269 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000015 (DEBUG)                      0x0
 0x00000003 (PLTGOT)                     0x851c000
 0x00000002 (PLTRELSZ)                   3512 (bytes)
 0x00000014 (PLTREL)                     REL
 0x00000017 (JMPREL)                     0x8088e6c
 0x00000011 (REL)                        0x8088c8c
 0x00000012 (RELSZ)                      480 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x6ffffffe (VERNEED)                    0x8088aec
 0x6fffffff (VERNEEDNUM)                 6
 0x6ffffff0 (VERSYM)                     0x8085b72
 0x00000000 (NULL)                       0x0
# readelf -d `gcc -print-file-name=libreadline.so.6`

Dynamic section at offset 0x38b88 contains 26 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libtinfo.so.5]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x0000000e (SONAME)                     Library soname: [libreadline.so.6]
 0x0000000c (INIT)                       0xb0fc
 0x0000000d (FINI)                       0x2e484
 0x00000019 (INIT_ARRAY)                 0x39278
 0x0000001b (INIT_ARRAYSZ)               4 (bytes)
 0x0000001a (FINI_ARRAY)                 0x3927c
 0x0000001c (FINI_ARRAYSZ)               4 (bytes)
 0x6ffffef5 (GNU_HASH)                   0x138
 0x00000005 (STRTAB)                     0x45ec
 0x00000006 (SYMTAB)                     0x15ac
 0x0000000a (STRSZ)                      13381 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000003 (PLTGOT)                     0x3a000
 0x00000002 (PLTRELSZ)                   848 (bytes)
 0x00000014 (PLTREL)                     REL
 0x00000017 (JMPREL)                     0xadac
 0x00000011 (REL)                        0x80dc
 0x00000012 (RELSZ)                      11472 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x6ffffffe (VERNEED)                    0x803c
 0x6fffffff (VERNEEDNUM)                 1
 0x6ffffff0 (VERSYM)                     0x7a32
 0x6ffffffa (RELCOUNT)                   1172
 0x00000000 (NULL)                       0x0
# readelf -d `gcc -print-file-name=libpthread.so.0` 

Dynamic section at offset 0x18eb8 contains 31 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x00000001 (NEEDED)                     Shared library: [ld-linux.so.2]
 0x0000000e (SONAME)                     Library soname: [libpthread.so.0]
 0x0000000c (INIT)                       0x433c
 0x0000000d (FINI)                       0x114d0
 0x00000019 (INIT_ARRAY)                 0x18dc8
 0x0000001b (INIT_ARRAYSZ)               8 (bytes)
 0x0000001a (FINI_ARRAY)                 0x18dd0
 0x0000001c (FINI_ARRAYSZ)               4 (bytes)
 0x00000004 (HASH)                       0x16e34
 0x6ffffef5 (GNU_HASH)                   0x198
 0x00000005 (STRTAB)                     0x24b4
 0x00000006 (SYMTAB)                     0xed4
 0x0000000a (STRSZ)                      5163 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000003 (PLTGOT)                     0x19000
 0x00000002 (PLTRELSZ)                   616 (bytes)
 0x00000014 (PLTREL)                     REL
 0x00000017 (JMPREL)                     0x40d4
 0x00000011 (REL)                        0x3e7c
 0x00000012 (RELSZ)                      600 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x6ffffffc (VERDEF)                     0x3b9c
 0x6ffffffd (VERDEFNUM)                  16
 0x0000001e (FLAGS)                      STATIC_TLS
 0x6ffffffb (FLAGS_1)                    Flags: NODELETE INITFIRST
 0x6ffffffe (VERNEED)                    0x3dcc
 0x6fffffff (VERNEEDNUM)                 2
 0x6ffffff0 (VERSYM)                     0x38e0
 0x6ffffffa (RELCOUNT)                   63
 0x00000000 (NULL)                       0x0

NOTE: 有个比较奇怪的库 linux-gate.so.1 , 其实它不是一个真正的动态库,它是不存在flash上的。

leon@netview:~$ ldd  linux-gate.so.1
ldd: ./linux-gate.so.1: No such file or directory

leon@netview:~$ readelf -d `gcc -print-file-name=linux-gate.so.1`
readelf: Error: 'linux-gate.so.1': No such file

既然是不存在的库,那么为什么会出现在ldd命令显示的动态库列表中呢? 请参考 Linux Virtual Dynamic Shared Objects

Linux Virtual Dynamic Shared Objects
在早期的 x86 处理器中,用户程序与管理服务之间的通信通过软中断实现。 随着处理器速度的提高,这已成为一个严重的瓶颈。 自 Pentium® II 处理器开始,Intel® 引入了 Fast System Call 装置来提高系统调用速度, 即采用 SYSENTER 和 SYSEXIT 指令,而不是中断。
您所看到的 linux-vdso.so.1 是个虚拟库,或者说是 Virtual Dynamic Shared Object,它只存在于程序的地址空间当中。 在旧版本系统中该库为 linux-gate.so.1。 该虚拟库为用户程序以处理器可支持的最快的方式 (对于特定处理器,采用中断方式;对于大多数最新的处理器,采用快速系统调用方式) 访问系统函数提供了必要的逻辑 。

3. ldd脚本

脚本信息:

leon@netview:~$ 
leon@netview:~$ ls -al `which ldd`
-rwxr-xr-x 1 root root 5425 Jun 17 04:41 /usr/bin/ldd
leon@netview:~$ 
leon@netview:~$ 
leon@netview:~$ file `which ldd`
/usr/bin/ldd: Bourne-Again shell script, ASCII text executable
leon@netview:~$ 

脚本内容:

#! /bin/bash
# Copyright (C) 1996-2014 Free Software Foundation, Inc.
# This file is part of the GNU C Library.

# The GNU C Library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.

# The GNU C Library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.

# You should have received a copy of the GNU Lesser General Public
# License along with the GNU C Library; if not, see
# <http://www.gnu.org/licenses/>.


# This is the `ldd' command, which lists what shared libraries are
# used by given dynamically-linked executables.  It works by invoking the
# run-time dynamic linker as a command and setting the environment
# variable LD_TRACE_LOADED_OBJECTS to a non-empty value.

# We should be able to find the translation right at the beginning.
TEXTDOMAIN=libc
TEXTDOMAINDIR=/usr/share/locale

RTLDLIST="/lib/ld-linux.so.2 /lib64/ld-linux-x86-64.so.2 /libx32/ld-linux-x32.so.2"
warn=
bind_now=
verbose=

while test $# -gt 0; do
  case "$1" in
  --vers | --versi | --versio | --version)
    echo 'ldd (Ubuntu EGLIBC 2.19-0ubuntu6.13) 2.19'
    printf $"Copyright (C) %s Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
" "2014"
    printf $"Written by %s and %s.
" "Roland McGrath" "Ulrich Drepper"
    exit 0
    ;;
  --h | --he | --hel | --help)
    echo $"Usage: ldd [OPTION]... FILE...
      --help              print this help and exit
      --version           print version information and exit
  -d, --data-relocs       process data relocations
  -r, --function-relocs   process data and function relocations
  -u, --unused            print unused direct dependencies
  -v, --verbose           print all information
"
    printf $"For bug reporting instructions, please see:\\n%s.\\n" \
      "<https://bugs.launchpad.net/ubuntu/+source/eglibc/+bugs>"
    exit 0
    ;;
  -d | --d | --da | --dat | --data | --data- | --data-r | --data-re | \
  --data-rel | --data-relo | --data-reloc | --data-relocs)
    warn=yes
    shift
    ;;
  -r | --f | --fu | --fun | --func | --funct | --functi | --functio | \
  --function | --function- | --function-r | --function-re | --function-rel | \
  --function-relo | --function-reloc | --function-relocs)
    warn=yes
    bind_now=yes
    shift
    ;;
  -v | --verb | --verbo | --verbos | --verbose)
    verbose=yes
    shift
    ;;
  -u | --u | --un | --unu | --unus | --unuse | --unused)
    unused=yes
    shift
    ;;
  --v | --ve | --ver)
    echo >&2 $"ldd: option \`$1' is ambiguous"
    exit 1
    ;;
  --)       # Stop option processing.
    shift; break
    ;;
  -*)
    echo >&2 'ldd:' $"unrecognized option" "\`$1'"
    echo >&2 $"Try \`ldd --help' for more information."
    exit 1
    ;;
  *)
    break
    ;;
  esac
done

nonelf ()
{
  # Maybe extra code for non-ELF binaries.
  return 1;
}

add_env="LD_TRACE_LOADED_OBJECTS=1 LD_WARN=$warn LD_BIND_NOW=$bind_now"
add_env="$add_env LD_LIBRARY_VERSION=\$verify_out"
add_env="$add_env LD_VERBOSE=$verbose"
if test "$unused" = yes; then
  add_env="$add_env LD_DEBUG=\"$LD_DEBUG${LD_DEBUG:+,}unused\""
fi

# The following command substitution is needed to make ldd work in SELinux
# environments where the RTLD might not have permission to write to the
# terminal.  The extra "x" character prevents the shell from trimming trailing
# newlines from command substitution results.  This function is defined as a
# subshell compound list (using "(...)") to prevent parameter assignments from
# affecting the calling shell execution environment.
try_trace() (
  output=$(eval $add_env '"$@"' 2>&1; rc=$?; printf 'x'; exit $rc)
  rc=$?
  printf '%s' "${output%x}"
  return $rc
)

case $# in
0)
  echo >&2 'ldd:' $"missing file arguments"
  echo >&2 $"Try \`ldd --help' for more information."
  exit 1
  ;;
1)
  single_file=t
  ;;
*)
  single_file=f
  ;;
esac

result=0
for file do
  # We don't list the file name when there is only one.
  test $single_file = t || echo "${file}:"
  case $file in
  */*) :
       ;;
  *) file=./$file
     ;;
  esac
  if test ! -e "$file"; then
    echo "ldd: ${file}:" $"No such file or directory" >&2
    result=1
  elif test ! -f "$file"; then
    echo "ldd: ${file}:" $"not regular file" >&2
    result=1
  elif test -r "$file"; then
    RTLD=
    ret=1
    for rtld in ${RTLDLIST}; do
      if test -x $rtld; then
    dummy=`$rtld 2>&1` 
    if test $? = 127; then
      verify_out=`${rtld} --verify "$file"`
      ret=$?
      case $ret in
      [02]) RTLD=${rtld}; break;;
      esac
    fi
      fi
    done
    case $ret in
    0|2)
      try_trace "$RTLD" "$file" || result=1
      ;;
    1)
      # This can be a non-ELF binary or no binary at all.
      nonelf "$file" || {
    echo $"    not a dynamic executable"
    result=1
      }
      ;;
    *)
      echo 'ldd:' ${RTLD} $"exited with unknown exit code" "($ret)" >&2
      exit 1
      ;;
    esac
  else
    echo 'ldd:' $"error: you do not have read permission for" "\`$file'" >&2
    result=1
  fi
done

exit $result
# Local Variables:
#  mode:ksh
# End:
### ✅ 如何打包程序使程序包含所有所需文件而不用从系统读取? 要让一个 C++ 程序在目标机器上运行时 **不依赖系统已安装的共享(如 .so、.dll)**,必须将所需的以 **静态链接** 的方式打包进可执行文件中,通过 **捆绑动态库 + 修正加载路径** 的方式进行完整分发。 --- ## 🔍 问题本质:为什么程序需要外部? 当你编译 C++ 程序时: - 使用了标准(`libstdc++`)、C 运行时(`glibc`)、第三方(如 `OpenCV`, `Boost`)等。 - 默认情况下,这些是以 **动态链接(Dynamic Linking)** 方式引入的,即生成的可执行文件只记录“我需要某个 `.so` `.dll`”,但并不包含它本身。 - 运行时操作系统需在特定路径(如 `/usr/lib`, `C:\Windows\System32`)找到这些 → 若缺失则报错:“无法启动程序”、“缺少 xxx.dll” “libxxx.so: cannot open shared object file”。 --- ## ✅ 解决方案一:静态链接(Static Linking)—— 把直接“缝”进可执行文件 ### 🧩 原理 将所有依赖的代码在编译阶段复制并合并到最终的可执行文件中,生成一个 **独立的、自包含的二进制文件**。 ### ✅ 优点 - 单文件部署 - 不依赖目标系统是否安装对应 - 更容易跨机器迁移 ### ❌ 缺点 - 文件体积大 - 更新困难(改一个就得重编整个程序) - 某些系统不允许静态链接(如 glibc) --- ### 示例:使用 GCC 静态链接 C++ 程序 ```bash g++ -static -o myapp main.cpp utils.cpp \ -lSDL2 -lGL -lpthread ``` 📌 关键选项:`-static` 这会尽可能地将所有静态链接。 > ⚠️ 注意:不是所有都有静态版本(`.a` 文件),如果没有 `.a` 只有 `.so`,链接会失败。 --- ### 强制静态链接某些(混合模式) 如果你只想静态链接部分: ```bash g++ -o myapp main.cpp \ -Wl,-Bstatic -lboost_system -lboost_filesystem \ -Wl,-Bdynamic -lSDL2 -lpthread ``` 含义: - `-Wl,-Bstatic`:告诉链接器接下来的用静态方式链接 - `-Wl,-Bdynamic`:恢复为动态链接 --- ## ✅ 解决方案二:动态链接但自带文件(Bundle Libraries) 适用于无法完全静态链接的情况(例如使用 Qt、OpenGL、CUDA 等复杂框架)。 ### 方法步骤: 1. **将所有依赖的 `.dll`(Windows) `.so`(Linux)与可执行文件一起打包** 2. **设置运行时搜索路径**,让程序优先从本地目录加载 --- ### 🖥️ Windows 上的做法(推荐使用工具) #### 方法 A:手动复制 DLL 并同目录放置 ```text dist/ ├── myapp.exe ├── SDL2.dll ├── opengl32.dll ├── libgcc_s_seh-1.dll ├── libstdc++-6.dll └── libwinpthread-1.dll ``` 这些 DLL 可通过以下方式获取: - 使用 `Dependency Walker` `ldd myapp.exe` 查看依赖 - 从 MinGW/MSVC 安装目录提取 - 使用打包工具自动收集 #### 方法 B:使用 `windeployqt`(Qt 应用专用) ```bash windeployqt myapp.exe ``` 自动拷贝 Qt 所需的所有 DLL 到当前目录。 #### 方法 C:使用第三方打包工具 - [MTA (Microsoft Visual C++ Redistributable)](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist) - [NSIS](https://nsis.sourceforge.io/) + 脚本自动收集 DLL - [Cx_Freeze](https://cx-freeze.readthedocs.io/en/latest/)(也支持 C++ 封装应用) --- ### 🐧 Linux 上的做法:使用 `patchelf` 修改 RPATH 默认情况下,Linux 只从 `/lib`, `/usr/lib` 等系统路径加载 `.so`。我们可以通过修改 ELF 文件的 `RPATH`,使其优先从程序所在目录加载。 #### 步骤: 1. 创建目录结构: ```bash mkdir dist cp myapp dist/ cp /path/to/libmylib.so dist/libs/ ``` 2. 修改可执行文件查找路径: ```bash patchelf --set-rpath '$ORIGIN/libs' dist/myapp ``` 现在 `myapp` 会在其 `libs/` 子目录中查找依赖。 3. 验证: ```bash ldd dist/myapp ``` 应显示 `libmylib.so => ./libs/libmylib.so` --- ### 📦 工具推荐汇总 | 平台 | 工具 | 功能 | |------|------|------| | 所有平台 | CMake + CPack | 自动构建和打包 | | Windows | Dependency Walker / Dependencies | 分析 DLL 依赖 | | Linux | `ldd`, `objdump -p`, `readelf -d` | 查看动态依赖 | | Linux | `patchelf` | 修改 ELF 的 RPATH | | 跨平台 | AppImage(Linux) | 打包为单个可执行镜像 | | Windows | NSIS / Inno Setup | 制作安装包 | --- ## ✅ 示例:完整打包流程(Linux + 自带) ```bash #!/bin/bash # 编译程序(动态链接) g++ -o myapp main.cpp -lSDL2 -lGL # 创建发布目录 mkdir -p release/{libs,} # 复制可执行文件 cp myapp release/ # 收集依赖(排除系统核心) for lib in $(ldd myapp | grep '\/' | awk '{print $3}'); do if [[ "$lib" != "" && ! "$lib" =~ ^/usr/lib/(libc|libm|libpthread|libdl|librt) ]]; then cp "$lib" release/libs/ fi done # 设置 RPATH patchelf --set-rpath '$ORIGIN/libs' release/myapp echo "打包完成!运行:./release/myapp" ``` --- ## ✅ 特别提醒:关于 C++ 标准静态链接 GCC 中静态链接 `libstdc++` 需注意: ```bash g++ -static-libgcc -static-libstdc++ -o myapp main.cpp ``` - `-static-libgcc`:静态链接 GCC 运行时 - `-static-libstdc++`:静态链接 libstdc++ - 但仍可能动态链接 glibc(Linux 内核级限制) ⚠️ **Linux 下不能完全静态链接 glibc**(除非使用 `musl-gcc`) 替代方案:使用 **Alpine Linux + musl libc** 构建真正静态的程序。 --- ### C++ 示例代码(用于测试打包) ```cpp #include <iostream> #include <vector> #include <cmath> int main() { std::vector<double> data = {1.0, 2.0, 3.0, 4.0}; for (double x : data) { std::cout << "sin(" << x << ") = " << sin(x) << std::endl; } return 0; } ``` 编译为静态可执行文件: ```bash g++ -static -o demo demo.cpp -lm ``` 此时 `ldd demo` 显示:`not a dynamic executable` → 成功! --- ## ✅ 总结 > 要让程序不从系统读取文件,有两种主流方法: 1. **静态链接(推荐简单项目)** - 使用 `-static`、`-static-libstdc++` 等选项 - 生成单一可执行文件,便于分发 2. **动态链接 + 捆绑文件(适合大型框架)** - 将 `.dll` `.so` 与程序一同打包 - 修改加载路径(Windows 同目录 / Linux 用 `patchelf` 设置 `$ORIGIN`) 选择哪种方式取决于你的目标平台、的可用性和部署需求。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值