lddtree 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. #!/bin/sh
  2. # Copyright 2007-2013 Gentoo Foundation
  3. # Copyright 2007-2013 Mike Frysinger <vapier@gentoo.org>
  4. # Copyright 2014-2015 Natanael Copa <ncopa@alpinelinux.org>
  5. # Distributed under the terms of the GNU General Public License v2
  6. argv0=${0##*/}
  7. version=1.26
  8. : ${ROOT:=/}
  9. [ "${ROOT}" = "${ROOT%/}" ] && ROOT="${ROOT}/"
  10. [ "${ROOT}" = "${ROOT#/}" ] && ROOT="${PWD}/${ROOT}"
  11. usage() {
  12. cat >&2 <<-EOF
  13. Display ELF dependencies as a tree
  14. Usage: ${argv0} [options] <ELF file[s]>
  15. Options:
  16. -a Show all duplicated dependencies
  17. -x Run with debugging
  18. -R <root> Use this ROOT filesystem tree
  19. --no-auto-root Do not automatically prefix input ELFs with ROOT
  20. -l Display output in a flat format
  21. -h Show this help output
  22. -V Show version information
  23. EOF
  24. exit ${1:-0}
  25. }
  26. version() {
  27. exec echo "lddtree-${version}"
  28. }
  29. error() {
  30. echo "${argv0}: $*" 1>&2
  31. ret=1
  32. return 1
  33. }
  34. # functions for scanelf backend
  35. elf_rpath_scanelf() {
  36. scanelf -qF '#F%r' "$@"
  37. }
  38. elf_interp_scanelf() {
  39. scanelf -qF '#F%i' "$@"
  40. }
  41. elf_needed_scanelf() {
  42. scanelf -qF '#F%n' "$@"
  43. }
  44. elf_specs_scanelf() {
  45. # %a = machine (EM) type
  46. # %M = EI class
  47. # %D = endian
  48. # %I = osabi
  49. # With glibc, the NONE, SYSV, GNU, and LINUX OSABI's are compatible.
  50. # LINUX and GNU are the same thing, as are NONE and SYSV, so normalize
  51. # GNU & LINUX to NONE. #442024 #464380
  52. scanelf -BF '#F%a %M %D %I' "$1" | \
  53. sed -r 's: (LINUX|GNU)$: NONE:'
  54. }
  55. # functions for binutils backend
  56. elf_rpath_binutils() {
  57. objdump -x "$@" | awk '$1 == "RUNPATH" { if($2!="") {runpath=$2;} } $1 == "RPATH" { if($2!="") {rpath=$2;} } END { if(runpath!="") {print runpath;} else {print rpath;} }'
  58. }
  59. elf_interp_binutils() {
  60. # readelf -p .interp ouputs:
  61. #
  62. # String dump of section '.interp':
  63. # [ 0] /lib/ld-musl-x86_64.so.1
  64. #
  65. readelf -p .interp "$1" | sed -E -n '/\[\s*[0-9]\]/s/^\s*\[.*\]\s*(.*)/\1/p'
  66. }
  67. elf_needed_binutils() {
  68. objdump -x "$@" | awk '$1 == "NEEDED" { line=line sep $2 ; sep="," } END { print line }'
  69. }
  70. elf_specs_binutils() {
  71. # get Class, Data, Machine and OS/ABI.
  72. # the OS/ABI 'GNU', 'System V' and 'Linux' are compatible so normalize
  73. readelf -h "$1" \
  74. | awk -F: '$1 ~ /Class|Data|Machine|OS.ABI/ {gsub(/^ +/, "", $2); print $2}' \
  75. | sed -E -e 's/UNIX - (System V|Linux|GNU)/UNIX/' \
  76. | tr '\n' ' '
  77. }
  78. # elf wrapper functions
  79. elf_rpath() { elf_rpath_$BACKEND "$@"; }
  80. elf_interp() { elf_interp_$BACKEND "$@"; }
  81. elf_needed() { elf_needed_$BACKEND "$@"; }
  82. elf_specs() { elf_specs_$BACKEND "$1"; }
  83. unset lib_paths_fallback
  84. for p in ${ROOT}lib* ${ROOT}usr/lib* ${ROOT}usr/local/lib*; do
  85. lib_paths_fallback="${lib_paths_fallback}${lib_paths_fallback:+:}${p}"
  86. done
  87. c_ldso_paths_loaded='false'
  88. find_elf() {
  89. _find_elf=''
  90. local elf=$1 needed_by=$2
  91. if [ "${elf}" != "${elf##*/}" ] && [ -e "${elf}" ] ; then
  92. _find_elf=${elf}
  93. return 0
  94. else
  95. check_paths() {
  96. local elf="$1"
  97. local pathstr="$2"
  98. IFS=:
  99. set -- $pathstr
  100. unset IFS
  101. local path pe
  102. for path ; do
  103. : ${path:=${PWD}}
  104. if [ "${path#${ROOT}}" = "${path}" ]; then
  105. path="${ROOT}${path#/}"
  106. fi
  107. pe="${path%/}/${elf#/}"
  108. if [ -e "${pe}" ] ; then
  109. if [ "$(elf_specs "${pe}")" = "${elf_specs}" ] ; then
  110. _find_elf=${pe}
  111. return 0
  112. fi
  113. fi
  114. done
  115. return 1
  116. }
  117. if [ "${c_last_needed_by}" != "${needed_by}" ] ; then
  118. c_last_needed_by="${needed_by}"
  119. c_last_needed_by_rpaths=$(elf_rpath "${needed_by}" | \
  120. sed -e "s:[$]ORIGIN:${needed_by%/*}:")
  121. fi
  122. if [ -n "${c_last_needed_by_rpaths}" ]; then
  123. check_paths "${elf}" "${c_last_needed_by_rpaths}" && return 0
  124. fi
  125. if [ -n "${LD_LIBRARY_PATH}" ] ; then
  126. check_paths "${elf}" "${LD_LIBRARY_PATH}"
  127. fi
  128. if ! ${c_ldso_paths_loaded} ; then
  129. c_ldso_paths_loaded='true'
  130. c_ldso_paths=
  131. if [ -r ${ROOT}etc/ld.so.conf ] ; then
  132. read_ldso_conf() {
  133. local line p
  134. for p ; do
  135. # if the glob didnt match anything #360041,
  136. # or the files arent readable, skip it
  137. [ -r "${p}" ] || continue
  138. while read line ; do
  139. case ${line} in
  140. "#"*) ;;
  141. "include "*) read_ldso_conf ${line#* } ;;
  142. *) c_ldso_paths="$c_ldso_paths:${ROOT}${line#/}";;
  143. esac
  144. done <"${p}"
  145. done
  146. }
  147. # the 'include' command is relative
  148. local _oldpwd="$PWD"
  149. cd "$ROOT"etc >/dev/null
  150. read_ldso_conf "${ROOT}"etc/ld.so.conf
  151. cd "$_oldpwd"
  152. fi
  153. fi
  154. if [ -n "${c_ldso_paths}" ] ; then
  155. check_paths "${elf}" "${c_ldso_paths}" && return 0
  156. fi
  157. check_paths "${elf}" "${lib_paths_ldso:-${lib_paths_fallback}}" && return 0
  158. fi
  159. return 1
  160. }
  161. list_existing_file() {
  162. if [ -e "$1" ]; then
  163. echo "$1"
  164. else
  165. echo "$1: Not found." >&2
  166. fi
  167. }
  168. # echo all intermediate symlinks and return the resolved path in
  169. # global variable _resolv_links
  170. resolv_links() {
  171. _resolv_links="$1"
  172. _list_files="$2"
  173. local oldpwd="$PWD"
  174. [ "$_list_files" = yes ] && list_existing_file "${_resolv_links}"
  175. cd "${_resolv_links%/*}"
  176. while [ -L "$_resolv_links" ]; do
  177. _resolv_links=$(readlink "$_resolv_links")
  178. case "$_resolv_links" in
  179. /*) _resolv_links="${ROOT}${_resolv_links#/}"
  180. cd "${_resolv_links%/*}"
  181. ;;
  182. */*) cd "${_resolv_links%/*}"
  183. ;;
  184. esac
  185. _resolv_links=$(pwd -P)/${_resolv_links##*/}
  186. [ "$_list_files" = yes ] && list_existing_file "${_resolv_links}"
  187. done
  188. cd "$oldpwd"
  189. }
  190. show_elf() {
  191. local elf=$1 indent=$2 parent_elfs=$3
  192. local rlib lib libs
  193. local interp resolved
  194. find_elf "${elf}"
  195. resolved=${_find_elf}
  196. elf=${elf##*/}
  197. ${LIST} || printf "%${indent}s%s => " "" "${elf}"
  198. case ",${parent_elfs}," in
  199. *,${elf},*)
  200. ${LIST} || printf "!!! circular loop !!!\n" ""
  201. return
  202. ;;
  203. esac
  204. parent_elfs="${parent_elfs},${elf}"
  205. if ${LIST} ; then
  206. resolv_links "${resolved:-$1}" yes
  207. else
  208. resolv_links "${resolved:-$1}" no
  209. printf "${resolved:-not found}"
  210. fi
  211. resolved=${_resolv_links}
  212. if [ ${indent} -eq 0 ] ; then
  213. elf_specs=$(elf_specs "${resolved}")
  214. interp=$(elf_interp "${resolved}")
  215. # ignore interpreters that do not have absolute path
  216. [ "${interp#/}" = "${interp}" ] && interp=
  217. [ -n "${interp}" ] && interp="${ROOT}${interp#/}"
  218. if ${LIST} ; then
  219. [ -n "${interp}" ] && resolv_links "${interp}" yes
  220. else
  221. printf " (interpreter => ${interp:-none})"
  222. fi
  223. if [ -r "${interp}" ] ; then
  224. # Extract the default lib paths out of the ldso.
  225. lib_paths_ldso=$(
  226. strings "${interp}" | \
  227. sed -nr -e "/^\/.*lib/{s|^/?|${ROOT}|;s|/$||;s|/?:/?|:${ROOT}|g;p}" | \
  228. tr '\n' ':'
  229. )
  230. fi
  231. interp=${interp##*/}
  232. fi
  233. ${LIST} || printf "\n"
  234. [ -z "${resolved}" ] && return
  235. libs=$(elf_needed "${resolved}")
  236. local my_allhits
  237. if ! ${SHOW_ALL} ; then
  238. my_allhits="${allhits}"
  239. allhits="${allhits},${interp},${libs}"
  240. fi
  241. oifs="$IFS"
  242. IFS=,
  243. set -- ${libs}
  244. IFS="$oifs"
  245. for lib; do
  246. lib=${lib##*/}
  247. case ",${my_allhits}," in
  248. *,${lib},*) continue;;
  249. esac
  250. find_elf "${lib}" "${resolved}"
  251. rlib=${_find_elf}
  252. show_elf "${rlib:-${lib}}" $((indent + 4)) "${parent_elfs}"
  253. done
  254. }
  255. SHOW_ALL=false
  256. SET_X=false
  257. LIST=false
  258. AUTO_ROOT=true
  259. while getopts haxVR:l-: OPT ; do
  260. case ${OPT} in
  261. a) SHOW_ALL=true;;
  262. x) SET_X=true;;
  263. h) usage;;
  264. V) version;;
  265. R) ROOT="${OPTARG%/}/";;
  266. l) LIST=true;;
  267. -) # Long opts ftw.
  268. case ${OPTARG} in
  269. no-auto-root) AUTO_ROOT=false;;
  270. *) usage 1;;
  271. esac
  272. ;;
  273. ?) usage 1;;
  274. esac
  275. done
  276. shift $(( $OPTIND - 1))
  277. [ -z "$1" ] && usage 1
  278. ${SET_X} && set -x
  279. if which scanelf >/dev/null 2>&1; then
  280. BACKEND=scanelf
  281. elif which objdump >/dev/null 2>&1 && which readelf >/dev/null 2>&1; then
  282. BACKEND=binutils
  283. else
  284. error "This tool needs either scanelf or binutils (objdump and readelf)"
  285. exit 1
  286. fi
  287. ret=0
  288. for elf ; do
  289. unset lib_paths_ldso
  290. unset c_last_needed_by
  291. if ${AUTO_ROOT} && [ -z "${elf##/*}" ] ; then
  292. elf="${ROOT}${elf#/}"
  293. fi
  294. if [ ! -e "${elf}" ] ; then
  295. error "${elf}: file does not exist"
  296. elif [ ! -r "${elf}" ] ; then
  297. error "${elf}: file is not readable"
  298. elif [ -d "${elf}" ] ; then
  299. if $LIST; then
  300. echo ${elf}
  301. else
  302. error "${elf}: is a directory"
  303. fi
  304. else
  305. allhits=""
  306. [ "${elf##*/*}" = "${elf}" ] && elf="./${elf}"
  307. show_elf "${elf}" 0 ""
  308. fi
  309. done
  310. exit ${ret}