1. 背景
在开发某嵌入式平台时,发现没有动态库依赖查看工具ldd,后来几经搜索与源码分析,将相关记录一下。
2. 查看ldd源码
-
ldd 指令本质
-
ldd 本质不是一个可执行程序,而是 shell脚本,可以使用 file ldd进行确认
# 查看ldd指令的路径 which ldd # 显示如下: /usr/bin/ldd # 查看ldd文件类型 file /usr/bin/ldd # 显示如下: /usr/bin/ldd: Bourne-Again shell script, ASCII text executable
-
-
ldd 源码
-
可以在
usr/bin
目录下查看源码cat /usr/bin/ldd
-
也可以下载 glibc 源码,编译后,在安装目录的 bin 目录下有对应的源码
-
3. ldd 源码分析
这里我们使用 cat /usr/bin/ldd
指令查看源码
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=
其中第4行指定了 ld动态库的列表,最终是通过这个列表中的某一个来显示可执行程序的动态库依赖的。
while test $# -gt 0; do
case "$1" in
--vers | --versi | --versio | --version)
echo 'ldd (Ubuntu GLIBC 2.31-0ubuntu9.9) 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
上面就是对 ldd
命令行参数进行分析,其中 $#
表示所有命令行的参数,匹配到对应选项后,会把对应变量设置为yes
下面是翻译后的提示
wangzhonglai@shell:~/learn/c_test$ ldd --help
用法:ldd [选项]… 文件…
--help 印出这份说明然后离开
--version 印出版本信息然后离开
-d, --data-relocs 进程数据重寻址
-r, --function-relocs 进程数据和函数重寻址
-u, --unused 印出未使用的直接依赖关系
-v, --verbose 印出所有信息
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
上面代码设置了环境变量,用于ld.so解析可执行文件时使用
try_trace() (
output=$(eval $add_env '"$@"' 2>&1; rc=$?; printf 'x'; exit $rc)
rc=$?
printf '%s' "${output%x}"
return $rc
)
eval指令是一个bash内置命令,用于将参数作为命令执行。在这段代码中,eval被用于执行ldd命令,并将add_env和"$@"作为参数传递给它。这样做的好处是可以动态地构建命令行参数,从而实现更灵活的命令执行。
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
上述指令用于判断 ldd 后面跟的文件个数,是单个还是多个,并设置对应的标记。
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
上述代码 其中 for file do
这个语句表示遍历所有的 $file
,等价于 for file in "$@"
,相当于对终端输入的参数进行遍历。
第5-9行判断是否是绝对路径,不是绝对路径的话,以当前路径寻找文件。
第11-17行,先判断文件是否存在,是否是一个普通文件,是否可读,满足条件之后,进行后续测试。
第20-30行对 $RTLDLIST ld.so 列表进行查询,确认可以使用的 ld.so,保存在 "$RTLD"中。
verify_out=${rtld} --verify "$file"
会使用ld.so对二进制文件进行校验,例如使用/lib/ld-linux.so.2 --verify /usr/bin/cat
会对 cat 指令进行分析,检测当前系统中加载的所有动态库是否存在缺失的符号、函数或变量,可以帮助我们解决动态库加载出错的问题。
第31-52行会对上面校验的结果进行确认,只有正确的情况下(也就是 $ret 为 0|2 的时候),会调用 try_trace
函数进行解析,try_trace
函数使用了一个环境变量LD_TRACE_LOADED_OBJECTS
,并且将环境变量设置为 true
。
在 Linux 系统上,动态链接器(ld.so)用于在程序运行时加载和解析库文件,以及为这些库提供符号链接。当您运行一个程序时,系统会自动查找它所依赖的共享库,以及这些库所依赖的其他库。使用 LD_TRACE_LOADED_OBJECTS=1
环境变量可以显示某个程序运行时所加载的所有共享库(也就是动态链接库)的路径。
4. ldd 指令移植
经过上述分析,ldd
指令最终是设置了一个LD_TRACE_LOADED_OBJECTS=1
,然后使用 xx_ld.so
进行显示可执行程序的动态依赖。因此,我们可以在RTLDLIST
链表中添加我们实际依赖的交叉编译环境下的ld.so
,比如ld-linux-armhf.so.3
。
5. 总结
- 本文介绍了
ldd
指令的源码位置以及查看方法; - 本文分析了
ldd
脚本文件的实际执行流程; - 本文介绍了在交叉编译环境下移植
ldd
指令的方法。