ldd 了解

ldd(List Dynamic Dependencies)是 Linux/Unix 系统下用于** 打印程序或共享库所依赖的共享对象(Shared Objects)**的命令行工具。它通过扫描二进制文件,列出其运行所需的 .so 库文件及其在系统中的具体路径,是排查“库缺失”或“版本冲突”问题的首选工具。


ldd 原理

ldd 本质上是一个 Shell 脚本(可以尝试 which ldd 然后 cat 查看其内容,见文末ldd脚本内容章节)。

  1. 核心机制:它通过设置特定的环境变量(主要是 LD_TRACE_LOADED_OBJECTS=1)来诱导动态链接器(如 /lib64/ld-linux-x86-64.so.2)工作。
  2. 模拟装载:当链接器检测到该变量时,它会加载目标程序并解析所有依赖库,但不会真正执行程序的主逻辑。
  3. 安全风险:对于某些极其特殊的二进制文件,ldd 可能会尝试直接执行代码。因此,严禁对未经信任的第三方可执行文件使用 ldd。更安全的替代方案是使用 readelf -d

ldd 语法规范

1
2
3
4
5
6
7
ldd [选项] [文件路径]

-v (--verbose):打印详细信息,包括符号的版本信息。
-u (--unused):显示已载入但在执行中未使用的直接依赖(用于精简库)。
-d (--data-relocs):执行数据重定位,报告缺失的函数对象。
-r (--function-relocs):执行数据和函数重定位,报告缺失的共享对象或函数。


举例解释

假设查看 /bin/ls 的依赖:

1
ldd /bin/ls

输出示例:

1
2
3
4
linux-vdso.so.1 (0x00007ffd415a1000)                               # 内核提供的虚拟系统调用库
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00...) # 库名 => 映射路径
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00...) # 标准 C 库
/lib64/ld-linux-x86-64.so.2 (0x00...) # 动态链接器本身
  • => 左侧:程序要求的库名称。
  • => 右侧:系统实际找到的库文件绝对路径。
  • 括号内:该库加载时的虚拟内存起始地址。

常用命令与示例

  1. 查找缺失的依赖库
    如果输出中出现 not found,说明程序无法运行。
1
2
# 示例:库文件未安装或不在搜索路径中
libsample.so => not found
  1. 查看详细的版本要求
    当遇到 “GLIBC_2.34 not found” 报错时使用。
1
ldd -v /usr/bin/python3
  1. 检查程序是否包含不必要的库
1
ldd -u my_app

高级用法

  1. 配合环境变量调试
    如果想临时测试一个非标准路径下的库,可以结合 LD_LIBRARY_PATH:
1
LD_LIBRARY_PATH=/opt/my_lib/ ldd ./my_app
  1. 深度排查符号缺失
    如果你能运行程序但报错 “undefined symbol”,可以使用 -r 强制进行函数重定位:
1
ldd -r ./my_broken_app
  1. 安全替代方案:readelf
    由于 ldd 存在执行风险,在生产环境或面对未知二进制文件时,推荐使用 readelf (Linux Man Page):
1
2
# 查看依赖库列表而不执行程序
readelf -d /bin/ls | grep NEEDED
  1. 解决 “not found” 的思路
    将库路径添加到 LD_LIBRARY_PATH 环境变量。
    或者将路径写入 /etc/ld.so.conf 并运行 sudo ldconfig 更新缓存。

ldd 脚本内容

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#! /bin/bash
# Copyright (C) 1996-2020 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
# <https://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 GLIBC 2.31-0ubuntu9.18) 2.31'
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.
" "2020"
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/glibc/+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
1)
# This can be a non-ELF binary or no binary at all.
nonelf "$file" || {
echo $" not a dynamic executable" >&2
result=1
}
;;
0|2)
try_trace "$RTLD" "$file" || 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: