文章目录
- 1. 背景
- 2. 查看ldd源码
- 3. ldd 源码分析
- 4. ldd 指令移植
- 5. 总结
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/localeRTLDLIST="/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; docase "$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=yesshift;;-r | --f | --fu | --fun | --func | --funct | --functi | --functio | \--function | --function- | --function-r | --function-re | --function-rel | \--function-relo | --function-reloc | --function-relocs)warn=yesbind_now=yesshift;;-v | --verb | --verbo | --verbos | --verbose)verbose=yesshift;;-u | --u | --un | --unu | --unus | --unuse | --unused)unused=yesshift;;--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; thenadd_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;;esacif test ! -e "$file"; thenecho "ldd: ${file}:" $"No such file or directory" >&2result=1elif test ! -f "$file"; thenecho "ldd: ${file}:" $"not regular file" >&2result=1elif test -r "$file"; thenRTLD=ret=1for rtld in ${RTLDLIST}; doif test -x $rtld; thendummy=`$rtld 2>&1`if test $? = 127; thenverify_out=`${rtld} --verify "$file"`ret=$?case $ret in[02]) RTLD=${rtld}; break;;esacfifidonecase $ret in1)# This can be a non-ELF binary or no binary at all.nonelf "$file" || {echo $" not a dynamic executable" >&2result=1};;0|2)try_trace "$RTLD" "$file" || result=1;;*)echo 'ldd:' ${RTLD} $"exited with unknown exit code" "($ret)" >&2exit 1;;esacelseecho 'ldd:' $"error: you do not have read permission for" "\`$file'" >&2result=1fi
doneexit $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
指令的方法。