| 1 | #!/bin/bash
|
|---|
| 2 | version="1.23"
|
|---|
| 3 | export LC_ALL=C
|
|---|
| 4 | my_name="$( basename "${0}" )"
|
|---|
| 5 |
|
|---|
| 6 |
|
|---|
| 7 | print_error() {
|
|---|
| 8 | printf "%s: %s\n" "${my_name}" "$*" >&2
|
|---|
| 9 | }
|
|---|
| 10 |
|
|---|
| 11 | print_opt_error() {
|
|---|
| 12 | print_error "$@"
|
|---|
| 13 | printf "Try \`%s --help' for more information.\n" "${my_name}" >&2
|
|---|
| 14 | }
|
|---|
| 15 |
|
|---|
| 16 | do_trace() {
|
|---|
| 17 | local depth=0
|
|---|
| 18 |
|
|---|
| 19 | [ -z "${CT_XLDD_VERBOSE}" ] && return 0
|
|---|
| 20 |
|
|---|
| 21 | for((depth=0; "${#FUNCNAME[$((depth+1))]}" != 0; depth++)); do :; done
|
|---|
| 22 | printf "%*s" $((4*(depth-1))) "" >&2
|
|---|
| 23 | printf -- "$@" >&2
|
|---|
| 24 | }
|
|---|
| 25 |
|
|---|
| 26 | show_version() {
|
|---|
| 27 | # Fake a real ldd, just in case some dumb script would check
|
|---|
| 28 | cat <<_EOF_
|
|---|
| 29 | ldd (crosstool-NG) ${version}
|
|---|
| 30 | Copyright (C) 2010 "Yann E. MORIN" <yann.morin.1998@free.fr>
|
|---|
| 31 | This is free software; see the source for copying conditions. There is NO
|
|---|
| 32 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|---|
| 33 | Licensed under the GPLv2, see the file LICENSES in the top-directory of the
|
|---|
| 34 | sources for this package.
|
|---|
| 35 | _EOF_
|
|---|
| 36 | }
|
|---|
| 37 |
|
|---|
| 38 | show_help() {
|
|---|
| 39 | cat <<_EOF_
|
|---|
| 40 | Usage: ${my_name} [OPTION]... --root DIR FILE...
|
|---|
| 41 | --help print this help and exit
|
|---|
| 42 | --version print version information and exit
|
|---|
| 43 | --root dir treat dir as being the root of the target
|
|---|
| 44 | -s, --show-system mark libs from the sysroot with a trailing '[*]'
|
|---|
| 45 | and libs found via RPATH with a trailing '[+]'
|
|---|
| 46 |
|
|---|
| 47 | _EOF_
|
|---|
| 48 | cat <<_EOF_ |fmt
|
|---|
| 49 | ${my_name} tries to mimick the behavior of a real native ldd, but can be
|
|---|
| 50 | used in a cross-development environment. Here is how it differs from a
|
|---|
| 51 | real native ldd:
|
|---|
| 52 |
|
|---|
| 53 | This program expects the environment variables \$CC and \$READELF defined
|
|---|
| 54 | with the name of the program (in the path) that should be used to build
|
|---|
| 55 | binaries for the target architecture and a working readelf program for
|
|---|
| 56 | that architecture.
|
|---|
| 57 |
|
|---|
| 58 | Example:
|
|---|
| 59 |
|
|---|
| 60 | CC=mipsel-buildroot-linux-gnu-gcc
|
|---|
| 61 | READELF=mipsel-buildroot-linux-gnu-readelf
|
|---|
| 62 |
|
|---|
| 63 | If any of this two variables are not defined, the program tries to guess the name
|
|---|
| 64 | of them using the environment variable CROSS_COMPILE. So on the example above you
|
|---|
| 65 | could just set
|
|---|
| 66 |
|
|---|
| 67 | CROSS_COMPILE=mipsel-buildroot-linux-gnu-
|
|---|
| 68 |
|
|---|
| 69 |
|
|---|
| 70 | If the CT_XLDD_VERBOSE variable is set and non-empty, then ${my_name} will
|
|---|
| 71 | print a lot of debug messages, explaining how it builds the library
|
|---|
| 72 | search path, and how each library was found and why.
|
|---|
| 73 |
|
|---|
| 74 | The LD_LIBRARY_PATH variable is not used, as it can not reliably be
|
|---|
| 75 | guessed except at runtime, and we can't run.
|
|---|
| 76 |
|
|---|
| 77 | ${my_name} does not scan /etc/ld.so.cache, but instead uses /etc/ld.so.conf
|
|---|
| 78 | (it understands the include directives therein for libces that have that).
|
|---|
| 79 |
|
|---|
| 80 | ${my_name} also interprets (tries to!) the RPATH/RUNPATH records found in
|
|---|
| 81 | the dynamic ELF section. Such paths are searched for only relative to
|
|---|
| 82 | the specified root, not from the sysroot (see below). Also, those paths
|
|---|
| 83 | are searched for not only for the file they appear in, but also for its
|
|---|
| 84 | dependencies.
|
|---|
| 85 |
|
|---|
| 86 | ${my_name} will search the directory specified with --root for libraries
|
|---|
| 87 | to resolve the NEEDED tags. If --root is not set, then ${my_name} will
|
|---|
| 88 | use the value in the environment variable \${CT_XLDD_ROOT}. If neither
|
|---|
| 89 | is set, then it will use as root the sysroot path from the cross-compiler.
|
|---|
| 90 | That it obtains by running "\${CC} -print-sysroot".
|
|---|
| 91 |
|
|---|
| 92 | If NEEDED libraries can't be found in the specified root directory, then
|
|---|
| 93 | ${my_name} will also look in the sysroot of the toolchain to see if it
|
|---|
| 94 | can find them.
|
|---|
| 95 |
|
|---|
| 96 | For NEEDED libraries that were found, the output will look like:
|
|---|
| 97 | libneeded.so => /path/to/libneeded.so (0xloadaddr)
|
|---|
| 98 |
|
|---|
| 99 | and for those that were not found, the output will look like:
|
|---|
| 100 | libneeded.so not found
|
|---|
| 101 |
|
|---|
| 102 | The expected load address 'loadaddr' is a faked address to match the output
|
|---|
| 103 | of the real ldd, but has no actual meaning (set to some constants for now,
|
|---|
| 104 | 0x8badf00d for libraries from the sysroot, 0xdeadc0de for those found via
|
|---|
| 105 | the RPATH/RUNPATH records, and 0xdeadbeef for others).
|
|---|
| 106 |
|
|---|
| 107 | _EOF_
|
|---|
| 108 |
|
|---|
| 109 | # Unimplemeted yet:
|
|---|
| 110 | # -d, --data-relocs process data relocations
|
|---|
| 111 | # -r, --function-relocs process data and function relocations
|
|---|
| 112 | # -u, --unused print unused direct dependencies
|
|---|
| 113 | # -v, --verbose print all information
|
|---|
| 114 |
|
|---|
| 115 | # See also this thread:
|
|---|
| 116 | # http://sourceware.org/ml/crossgcc/2008-09/msg00057.html
|
|---|
| 117 | }
|
|---|
| 118 |
|
|---|
| 119 | # Parse command line options
|
|---|
| 120 | root="${CT_XLDD_ROOT}"
|
|---|
| 121 | show_system=
|
|---|
| 122 | while true; do
|
|---|
| 123 | case "${1}" in
|
|---|
| 124 | --help)
|
|---|
| 125 | show_help
|
|---|
| 126 | exit 0
|
|---|
| 127 | ;;
|
|---|
| 128 | --version)
|
|---|
| 129 | show_version
|
|---|
| 130 | exit 0
|
|---|
| 131 | ;;
|
|---|
| 132 | --root)
|
|---|
| 133 | root="$2"
|
|---|
| 134 | shift
|
|---|
| 135 | ;;
|
|---|
| 136 | --root=*)
|
|---|
| 137 | root="${1#--root=}"
|
|---|
| 138 | ;;
|
|---|
| 139 | --show-system|-s)
|
|---|
| 140 | show_system=1
|
|---|
| 141 | ;;
|
|---|
| 142 | -*)
|
|---|
| 143 | print_opt_error "unrecognized option \`${1}'"
|
|---|
| 144 | exit 1
|
|---|
| 145 | ;;
|
|---|
| 146 | *)
|
|---|
| 147 | break
|
|---|
| 148 | ;;
|
|---|
| 149 | esac
|
|---|
| 150 | shift
|
|---|
| 151 | done
|
|---|
| 152 |
|
|---|
| 153 | # Sanity checks
|
|---|
| 154 |
|
|---|
| 155 | if [[ -z ${CC} ]]; then
|
|---|
| 156 | if [[ -n ${CROSS_COMPILE} ]]; then
|
|---|
| 157 | CC="${CROSS_COMPILE}gcc"
|
|---|
| 158 | else
|
|---|
| 159 | print_opt_error "Environment variable CC not defined. Please set it to your cross-compiler name"
|
|---|
| 160 | exit 1
|
|---|
| 161 | fi
|
|---|
| 162 | fi
|
|---|
| 163 |
|
|---|
| 164 | if [[ -z ${READELF} ]]; then
|
|---|
| 165 | if [[ -n ${CROSS_COMPILE} ]]; then
|
|---|
| 166 | READELF="${CROSS_COMPILE}readelf"
|
|---|
| 167 | else
|
|---|
| 168 | print_opt_error "Environment variable READELF not defined. Please set it to your cross-compiled readelf"
|
|---|
| 169 | exit 1
|
|---|
| 170 | fi
|
|---|
| 171 | fi
|
|---|
| 172 |
|
|---|
| 173 | if ! test -x "$(which ${READELF})"; then
|
|---|
| 174 | print_error "Cant find readelf program ${READELF} in PATH=${PATH}"
|
|---|
| 175 | exit 1
|
|---|
| 176 | fi
|
|---|
| 177 |
|
|---|
| 178 | fake_load_addr_root="$((0xdeadbeef))"
|
|---|
| 179 | fake_load_addr_rpath="$((0xdeadc0de))"
|
|---|
| 180 | fake_load_addr_sysroot="$((0x8badf00d))"
|
|---|
| 181 | ld_library_path="/lib:/usr/lib"
|
|---|
| 182 |
|
|---|
| 183 | bits="32"
|
|---|
| 184 | if "${CC}" -dM -E - < /dev/null | grep -q "#define __SIZEOF_POINTER__ 8"; then
|
|---|
| 185 | bits="64"
|
|---|
| 186 | fi
|
|---|
| 187 |
|
|---|
| 188 | sysroot="$( "${CC}" -print-sysroot 2>/dev/null )"
|
|---|
| 189 | if [ -z "${sysroot}" ]; then
|
|---|
| 190 | sysroot="$( "${CC}" -print-file-name=libc.so 2>/dev/null )"
|
|---|
| 191 | fi
|
|---|
| 192 | if [ -z "${sysroot}" ]; then
|
|---|
| 193 | print_error "unable to find sysroot for \`${CC}'"
|
|---|
| 194 |
|
|---|
| 195 | fi
|
|---|
| 196 |
|
|---|
| 197 | if [ -z "${root}" ]; then
|
|---|
| 198 | do_trace "root not defined. Assuming root = sysroot"
|
|---|
| 199 | root="${sysroot}"
|
|---|
| 200 | fi
|
|---|
| 201 |
|
|---|
| 202 | do_report_needed_found() {
|
|---|
| 203 | local needed="${1}"
|
|---|
| 204 | local path="${2}"
|
|---|
| 205 | local origin="${3}"
|
|---|
| 206 | local loadaddr
|
|---|
| 207 | local sys
|
|---|
| 208 |
|
|---|
| 209 | case "${origin}" in
|
|---|
| 210 | root)
|
|---|
| 211 | loadaddr="${fake_load_addr_root}"
|
|---|
| 212 | ;;
|
|---|
| 213 | rpath)
|
|---|
| 214 | loadaddr="${fake_load_addr_rpath}"
|
|---|
| 215 | if [ -n "${show_system}" ]; then
|
|---|
| 216 | sys=" [+]"
|
|---|
| 217 | fi
|
|---|
| 218 | ;;
|
|---|
| 219 | sysroot)
|
|---|
| 220 | loadaddr="${fake_load_addr_sysroot}"
|
|---|
| 221 | if [ -n "${show_system}" ]; then
|
|---|
| 222 | sys=" [*]"
|
|---|
| 223 | fi
|
|---|
| 224 | ;;
|
|---|
| 225 | esac
|
|---|
| 226 |
|
|---|
| 227 | printf "%8s%s => %s (0x%0*x)%s\n" \
|
|---|
| 228 | "" \
|
|---|
| 229 | "${needed}" \
|
|---|
| 230 | "${path}" \
|
|---|
| 231 | "$((bits/4))" \
|
|---|
| 232 | "${loadaddr}" \
|
|---|
| 233 | "${sys}"
|
|---|
| 234 | }
|
|---|
| 235 |
|
|---|
| 236 | joinpath() {
|
|---|
| 237 | echo "${1%%/}/${2##/}"
|
|---|
| 238 | }
|
|---|
| 239 |
|
|---|
| 240 | # Search a needed file, scanning ${lib_dir} in the root directory
|
|---|
| 241 | do_find_needed() {
|
|---|
| 242 | local needed="${1}"
|
|---|
| 243 | local -a list
|
|---|
| 244 | local -a dirs
|
|---|
| 245 | local found="false"
|
|---|
| 246 | local where
|
|---|
| 247 | local base
|
|---|
| 248 | local d i
|
|---|
| 249 |
|
|---|
| 250 | do_trace "Searching for '%s'\n" "${needed}"
|
|---|
| 251 |
|
|---|
| 252 | # rpath shall come first!
|
|---|
| 253 | list=( \
|
|---|
| 254 | "rpath:${root}" \
|
|---|
| 255 | "root:${root}" \
|
|---|
| 256 | "sysroot:${sysroot}" \
|
|---|
| 257 | )
|
|---|
| 258 |
|
|---|
| 259 | for i in "${list[@]}"; do
|
|---|
| 260 | where="${i%%:*}"
|
|---|
| 261 | base="${i#*:}"
|
|---|
| 262 | if [ "${where}" = "rpath" ]; then
|
|---|
| 263 | dirs=( "${search_rpath[@]}" )
|
|---|
| 264 | else
|
|---|
| 265 | dirs=( "${needed_search_path[@]}" )
|
|---|
| 266 | fi
|
|---|
| 267 | for d in "${dirs[@]}"; do
|
|---|
| 268 | do_trace "-> looking in '%s' (%s)\n" "${d}" "${where}"
|
|---|
| 269 |
|
|---|
| 270 | pathfound="$(joinpath ${d} ${needed})"
|
|---|
| 271 | if [ "${where}" != "rpath" ]; then
|
|---|
| 272 | pathfound="$(joinpath ${base} ${pathfound})"
|
|---|
| 273 | fi
|
|---|
| 274 |
|
|---|
| 275 | if [ -f "${pathfound}" ]; then
|
|---|
| 276 | found="true"
|
|---|
| 277 | do_trace "---> found\n"
|
|---|
| 278 | break 2
|
|---|
| 279 | fi
|
|---|
| 280 |
|
|---|
| 281 | done
|
|---|
| 282 | done
|
|---|
| 283 |
|
|---|
| 284 | if ${found}; then
|
|---|
| 285 | do_report_needed_found "${needed}" "${pathfound}" "${where}"
|
|---|
| 286 | do_process_file "${pathfound}"
|
|---|
| 287 | else
|
|---|
| 288 | printf "%8s%s not found\n" "" "${needed}"
|
|---|
| 289 | fi
|
|---|
| 290 |
|
|---|
| 291 | do_trace "Done searching for '%s'\n" "${needed}"
|
|---|
| 292 | }
|
|---|
| 293 |
|
|---|
| 294 | # Scan a file for all NEEDED tags
|
|---|
| 295 | do_process_file() {
|
|---|
| 296 | local file="${1}"
|
|---|
| 297 | local -a save_search_rpath
|
|---|
| 298 | local n m
|
|---|
| 299 | local found
|
|---|
| 300 |
|
|---|
| 301 | do_trace "Parsing file '%s'\n" "${file}"
|
|---|
| 302 |
|
|---|
| 303 | save_search_rpath=( "${search_rpath[@]}" )
|
|---|
| 304 | for n in $( "${READELF}" -d "${file}" \
|
|---|
| 305 | |grep -E '\((RPATH|RUNPATH)\)' \
|
|---|
| 306 | |sed -r -e 's/^.*Library r(|un)path:[[:space:]]+\[(.*)\]$/\2/;' \
|
|---|
| 307 | |sed "s/:/\ /g" \
|
|---|
| 308 | ); do
|
|---|
| 309 | do_trace "-> adding rpath '%s'\n" "${n}"
|
|---|
| 310 | search_rpath+=( "${n}" )
|
|---|
| 311 | done
|
|---|
| 312 | do_trace ": search path:\n"
|
|---|
| 313 | for n in "${search_rpath[@]}" "${needed_search_path[@]}"; do
|
|---|
| 314 | do_trace ": - '%s'\n" "${n}"
|
|---|
| 315 | done
|
|---|
| 316 | do_trace ": end search path\n"
|
|---|
| 317 |
|
|---|
| 318 | for n in $( "${READELF}" -d "${file}" \
|
|---|
| 319 | |grep -E '\(NEEDED\)' \
|
|---|
| 320 | |sed -r -e 's/^.*Shared library:[[:space:]]+\[([^]]+)\].*/\1/;' \
|
|---|
| 321 | ); do
|
|---|
| 322 | found=0
|
|---|
| 323 | for m in "${needed_list[@]}"; do
|
|---|
| 324 | [ "${n}" = "${m}" ] && found=1 && break
|
|---|
| 325 | done
|
|---|
| 326 | if [ ${found} -ne 0 ]; then
|
|---|
| 327 | do_trace "-> skipping already known dependency '%s'\n" "${n}"
|
|---|
| 328 | continue
|
|---|
| 329 | fi
|
|---|
| 330 | do_trace "-> handling new dependency '%s'\n" "${n}"
|
|---|
| 331 | needed_list+=( "${n}" )
|
|---|
| 332 | do_find_needed "${n}"
|
|---|
| 333 | do_trace "-> do_find_needed: ${n}"
|
|---|
| 334 | do_trace "-> done handling dependency '%s'\n" "${n}"
|
|---|
| 335 | done
|
|---|
| 336 |
|
|---|
| 337 | search_rpath=( "${save_search_rpath[@]}" )
|
|---|
| 338 |
|
|---|
| 339 | do_trace "Finished parsing file '%s'\n" "${file}"
|
|---|
| 340 | }
|
|---|
| 341 |
|
|---|
| 342 | # Recursively scan a /etc/ld.so.conf file
|
|---|
| 343 | do_scan_etc_ldsoconf() {
|
|---|
| 344 | local ldsoconf="${1}"
|
|---|
| 345 | local g
|
|---|
| 346 | local f
|
|---|
| 347 |
|
|---|
| 348 | [ -f "${ldsoconf}" ] || return 0
|
|---|
| 349 | do_trace "Parsing ld.so.conf: '%s'\n" "${ldsoconf}"
|
|---|
| 350 |
|
|---|
| 351 | while read line; do
|
|---|
| 352 | case "${line}" in
|
|---|
| 353 | include\ *)
|
|---|
| 354 | g="${root}${line#include }"
|
|---|
| 355 | do_trace "-> handling include directive '%s'\n" "${g}"
|
|---|
| 356 | for f in ${g}; do
|
|---|
| 357 | do_scan_etc_ldsoconf "${f}"
|
|---|
| 358 | done
|
|---|
| 359 | do_trace "-> finished handling include directive '%s'\n" "${g}"
|
|---|
| 360 | ;;
|
|---|
| 361 | \#*|"")
|
|---|
| 362 | ;;
|
|---|
| 363 | *)
|
|---|
| 364 | do_trace "-> adding search dir '%s'\n" "${line}"
|
|---|
| 365 | needed_search_path+=( "${line}" )
|
|---|
| 366 | ;;
|
|---|
| 367 | esac
|
|---|
| 368 | done <"${ldsoconf}"
|
|---|
| 369 |
|
|---|
| 370 | do_trace "Finished parsing ld.so.conf: '%s'\n" "${ldsoconf}"
|
|---|
| 371 | }
|
|---|
| 372 |
|
|---|
| 373 | # Build up the full list of search directories
|
|---|
| 374 | declare -a needed_search_path
|
|---|
| 375 | do_trace "Adding basic lib dirs\n"
|
|---|
| 376 | ld_library_path="${ld_library_path}:"
|
|---|
| 377 | while [ -n "${ld_library_path}" ]; do
|
|---|
| 378 | d="${ld_library_path%%:*}"
|
|---|
| 379 | if [ -n "${d}" ]; then
|
|---|
| 380 | do_trace "-> adding search dir '%s'\n" "${d}"
|
|---|
| 381 | needed_search_path+=( "${d}" )
|
|---|
| 382 | fi
|
|---|
| 383 | ld_library_path="${ld_library_path#*:}"
|
|---|
| 384 | done
|
|---|
| 385 | do_trace "Done adding basic lib dirs\n"
|
|---|
| 386 | do_trace "Scanning '/etc/ld.so.conf'\n"
|
|---|
| 387 | do_scan_etc_ldsoconf "${root}/etc/ld.so.conf"
|
|---|
| 388 | do_trace "Done scanning '/etc/ld.so.conf'\n"
|
|---|
| 389 | do_trace "Search path:\n"
|
|---|
| 390 | for p in "${needed_search_path[@]}"; do
|
|---|
| 391 | do_trace "-> '%s'\n" "${p}"
|
|---|
| 392 | done
|
|---|
| 393 |
|
|---|
| 394 | declare -a needed_list
|
|---|
| 395 | declare -a search_rpath
|
|---|
| 396 | do_trace "Scanning file '%s'\n" "${1}"
|
|---|
| 397 | if [ -z "${1}" ]; then
|
|---|
| 398 | print_opt_error "missing file arguments"
|
|---|
| 399 | exit 1
|
|---|
| 400 | fi
|
|---|
| 401 | do_process_file "${1}"
|
|---|
| 402 | do_trace "Done scanning file '%s'\n" "${1}"
|
|---|