/toolchain/scripts/xldd.in
Autoconf | 472 lines | 379 code | 53 blank | 40 comment | 59 complexity | b60eeef9f45eff14412b0e14859a4e5d MD5 | raw file
- #!@@CT_bash@@
- # NON-CONFIGURABLE STUFF!
- export LC_ALL=C
- version="@@CT_VERSION@@"
- bits="@@CT_BITS@@"
- sed="${SED:-@@CT_sed@@}"
- grep="${GREP:-@@CT_grep@@}"
- my_name="$( basename "${0}" )"
- prefix="${0%-ldd}"
- gcc="${prefix}-gcc"
- readelf="${prefix}-readelf"
- fake_load_addr_root="$((0xdeadbeef))"
- fake_load_addr_rpath="$((0xdeadc0de))"
- fake_load_addr_sysroot="$((0x8badf00d))"
- ld_library_path="/lib:/usr/lib"
- need_e_class=
- need_e_flags=
- need_e_mach=
- do_error() {
- printf "%s: %s\n" "${my_name}" "$*" >&2
- }
- do_opt_error() {
- do_error "$@"
- printf "Try \`%s --help' for more information\n" "${my_name}" >&2
- }
- do_trace() {
- local depth=0
- [ -z "${CT_XLDD_VERBOSE}" ] && return 0
- for((depth=0; "${#FUNCNAME[$((depth+1))]}" != 0; depth++)); do :; done
- printf "%*s" $((4*(depth-1))) "" >&2
- printf -- "$@" >&2
- }
- show_version() {
- # Fake a real ldd, just in case some dumb script would check
- cat <<_EOF_
- ldd (crosstool-NG) ${version}
- Copyright (C) 2010 "Yann E. MORIN" <yann.morin.1998@free.fr>
- This is free software; see the source for copying conditions. There is NO
- warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- Licensed under the GPLv2, see the file LICENSE in the top-directory of the
- sources for this package.
- _EOF_
- }
- show_help() {
- cat <<_EOF_
- Usage: ${my_name} [OPTION]... --root DIR FILE...
- --help print this help and exit
- --version print version information and exit
- --root dir treat dir as being the root of the target
- -s, --show-system mark libs from the sysroot with a trailing '[*]'
- and libs found via RPATH with a trailing '[+]'
- _EOF_
- cat <<_EOF_ |fmt
- ${my_name} tries to mimick the behavior of a real native ldd, but can be
- used in a cross-development environment. Here is how it differs from a
- real native ldd:
- If the CT_XLDD_VERBOSE variable is set and non-empty, then ${my_name} will
- print a lot of debug messages, explaining how it builds the library
- search path, and how each library was found and why.
- The LD_LIBRARY_PATH variable is not used, as it can not reliably be
- guessed except at runtime, and we can't run.
- ${my_name} does not scan /etc/ld.so.cache, but instead uses /etc/ld.so.conf
- (it understands the include directives therein for libces that have that).
- ${my_name} also interprets (tries to!) the RPATH/RUNPATH records found in
- the dynamic ELF section. Such paths are searched for only relative to
- the specified root, not from the sysroot (see below). Also, those paths
- are searched for not only for the file they appear in, but also for its
- dependencies.
- ${my_name} will search the directory specified with --root for libraries
- to resolve the NEEDED tags. If --root is not set, then ${my_name} will
- use the value in the environment variable \${CT_XLDD_ROOT}. If neither
- is set, then this is an error.
- If NEEDED libraries can't be found in the specified root directory, then
- ${my_name} will also look in the sysroot of the toolchain to see if it
- can find them.
- For NEEDED libraries that were found, the output will look like:
- libneeded.so => /path/to/libneeded.so (0xloadaddr)
- and for those that were not found, the output will look like:
- libneeded.so not found
- The paths are relative to the specified root directory, or to the sysroot
- (eg. /lib/libneeded.so, /usr/lib/libneeded.so, and so on...).
- The expected load address 'loadaddr' is a faked address to match the output
- of the real ldd, but has no actual meaning (set to some constants for now,
- 0x8badf00d for libraries from the sysroot, 0xdeadc0de for those found via
- the RPATH/RUNPATH records, and 0xdeadbeef for others).
- _EOF_
- # Unimplemeted yet:
- # -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
- # See also this thread:
- # http://sourceware.org/ml/crossgcc/2008-09/msg00057.html
- }
- # Parse command line options
- root="${CT_XLDD_ROOT}"
- show_system=
- while true; do
- case "${1}" in
- --help)
- show_help
- exit 0
- ;;
- --version)
- show_version
- exit 0
- ;;
- --root)
- root="$2"
- shift
- ;;
- --root=*)
- root="${1#--root=}"
- ;;
- --show-system|-s)
- show_system=1
- ;;
- -*)
- do_opt_error "unrecognized option \`${1}'"
- exit 1
- ;;
- *)
- break
- ;;
- esac
- shift
- done
- # Sanity checks
- if [ -z "${root}" ]; then
- do_opt_error "no root given"
- exit 1
- fi
- if [ ! -d "${root}" ]; then
- do_error "\`${root}': no such file or directory"
- exit 1
- fi
- sysroot="$( "${gcc}" -print-sysroot 2>/dev/null )"
- if [ -z "${sysroot}" ]; then
- sysroot="$( "${gcc}" -print-file-name=libc.so 2>/dev/null \
- |${sed} -r -e 's:/usr/lib/libc.so$::;' \
- )"
- fi
- if [ -z "${sysroot}" ]; then
- do_error "unable to find sysroot for \`${gcc}'"
- fi
- do_report_needed_found() {
- local needed="${1}"
- local path="${2}"
- local origin="${3}"
- local loadaddr
- local sys
- case "${origin}" in
- root)
- loadaddr="${fake_load_addr_root}"
- ;;
- rpath)
- loadaddr="${fake_load_addr_rpath}"
- if [ -n "${show_system}" ]; then
- sys=" [+]"
- fi
- ;;
- sysroot)
- loadaddr="${fake_load_addr_sysroot}"
- if [ -n "${show_system}" ]; then
- sys=" [*]"
- fi
- ;;
- esac
- printf "%8s%s => %s (0x%0*x)%s\n" \
- "" \
- "${needed}" \
- "${path}" \
- "$((bits/4))" \
- "${loadaddr}" \
- "${sys}"
- }
- # Helper: passed flags and needed flags must either both have
- # a given flag set, or neither must have it.
- flag_match() {
- local flags="${1}"
- local e_flags="${2}"
- local dso="${3}"
- local f
- for f in ${flags}; do
- case "${e_flags}:${need_e_flags}" in
- *,${f},*:*,${f},*)
- # Both have it, continue
- ;;
- *,${f},*)
- # Only one has it
- do_trace "-> skip incompatible '%s' (flags '%s', need '%s')" \
- "${dso}" "${e_flags}" "${need_e_flags}"
- return 1;;
- *) ;; # Neither one has it, continue
- esac
- done
- return 0
- }
- __warned_unknown_machine=no
- do_check_compat() {
- local file="${1}"
- local info e_mach e_class e_flags
- local dso
- local -a flags
- if [ ! -r "${file}" ]; then
- return 1
- fi
- info=`${readelf} -Wh "${file}"`
- e_class=`echo "${info}" | "${sed}" -nr 's/.*Class:[[:space:]]*//p'`
- e_mach=`echo "${info}" | "${sed}" -nr 's/.*Machine:[[:space:]]*//p'`
- e_flags=`echo "${info}" | "${sed}" -nr -e 's/$/,/' -e 's/, /,/g' -e 's/.*Flags:[[:space:]]*0x[0-9a-f]{1,}//p'`
- dso="${base}${d}/${needed}"
- if [ "${e_class}" != "${need_e_class}" ]; then
- do_trace "-> skip incompatible '%s' (class '%s', need '%s')\n" \
- "${dso}" "${e_class}" "${need_e_class}"
- return 1
- fi
- if [ "${e_mach}" != "${need_e_mach}" ]; then
- do_trace "-> skip incompatible '%s' (machine '%s', need '%s')\n" \
- "${dso}" "${e_mach}" "${need_e_mach}"
- return 1
- fi
- # ElfXX_Ehdr.e_flags is trickier, the rules for compatibility are
- # different for different architectures (machines). The logic is
- # the same as in elf_machine_matches_host() in GNU libc.
- case "${e_mach}" in
- AArch64|"Advanced Micro Devices X86-64"|Alpha|ARM|"Intel 80386"|\
- MC68000|"Xilinx MicroBlaze"|"Altera Nios II"|"Renesas / SuperH SH")
- # Simple cases: flags are not taken into the account
- ;;
- "MIPS R3000")
- # glibc just rejects "fp64", says it is unsupported on linux.
- # but who knows, maybe one day ct-ng will be targeting non-linux too.
- if ! flag_match "abi2 nan2008 fp64" "${e_flags}" "${dso}"; then
- return 1
- fi
- ;;
- PowerPC)
- # For 32-bit PowerPC, flags not checked. For 64-bit, check ABI version:
- # 0 means "not using any features affected by the differences"; v1 and v2
- # are the defined ABI versions. Here we have a different check than glibc:
- # in glibc, the dynamic linker checks the ABI compatibility against its own
- # ABI while we here check against the binary ABI. Doing it properly requires
- # a rework of this script, and the current check seems to work even with a
- # trivial "hello world" (it is reported as "abiv1", so apparently no
- # applications report 0 as ABI version).
- if [ "${e_class}" = "ELF64" ] && ! flag_match "abiv1 abiv2" "${e_flags}" "${dso}"; then
- return 1
- fi
- ;;
- "IBM S/390")
- # Dynamic linker checks against the kernel capability for "highgprs" flag
- # at runtime, but we cannot do that. Report success.
- ;;
- Sparc|"Sparc v8+"|"Sparc v9")
- # According to glibc sources, even DSOs reporting these different e_machine
- # values may be compatible. We currently don't handle that, but might need to.
- ;;
- *)
- # Warn once on an unhandled architecture and assume flags are compatible
- if [ "${__warned_unknown_machine}" = "no" ]; then
- __warned_unknown_machine=yes
- do_error "Architecture ${e_mach} not handled by cross-ldd script; assuming match"
- fi
- ;;
- esac
- return 0
- }
- # Search a needed file, scanning ${lib_dir} in the root directory
- do_find_needed() {
- local needed="${1}"
- local -a list
- local -a dirs
- local found
- local where
- local base
- local d i
- do_trace "Searching for '%s'\n" "${needed}"
- # rpath shall come first!
- list=( \
- "rpath:${root}" \
- "root:${root}" \
- "sysroot:${sysroot}" \
- )
- for i in "${list[@]}"; do
- where="${i%%:*}"
- base="${i#*:}"
- if [ "${where}" = "rpath" ]; then
- dirs=( "${search_rpath[@]}" )
- else
- dirs=( "${needed_search_path[@]}" )
- fi
- for d in "${dirs[@]}"; do
- do_trace "-> looking in '%s' (%s)\n" "${d}" "${where}"
- if do_check_compat "${base}${d}/${needed}"; then
- found="${d}/${needed}"
- do_trace "---> found\n"
- break 2
- fi
- done
- done
- if [ -n "${found}" ]; then
- do_report_needed_found "${needed}" "${found}" "${where}"
- do_process_file "${base}${found}"
- else
- printf "%8s%s not found\n" "" "${needed}"
- fi
- do_trace "Done searching for '%s'\n" "${needed}"
- }
- # Scan a file for all NEEDED tags
- do_process_file() {
- local file="${1}"
- local initial="${2}"
- local -a save_search_rpath
- local n m
- local found
- local info
- do_trace "Parsing file '%s'\n" "${file}"
- save_search_rpath=( "${search_rpath[@]}" )
- for n in $( "${readelf}" -d "${file}" \
- |"${grep}" -E '\((RPATH|RUNPATH)\)' \
- |"${sed}" -r -e 's/^.*Library r(|un)path:[[:space:]]+\[(.*)\]$/\2/;'\
- ); do
- do_trace "-> adding rpath '%s'\n" "${n}"
- search_rpath+=( "${n}" )
- done
- do_trace ": search path:\n"
- for n in "${search_rpath[@]}" "${needed_search_path[@]}"; do
- do_trace ": - '%s'\n" "${n}"
- done
- do_trace ": end search path\n"
- if [ -n "${initial}" ]; then
- if ! "${readelf}" -Wl "${file}" |
- "${grep}" 'Requesting program interpreter: ' >/dev/null; then
- printf " not a dynamic executable\n"
- exit 1
- fi
- info=`${readelf} -Wh "${file}"`
- need_e_class=`echo "${info}" | "${sed}" -nr 's/.*Class:[[:space:]]*//p'`
- need_e_mach=`echo "${info}" | "${sed}" -nr 's/.*Machine:[[:space:]]*//p'`
- need_e_flags=`echo "${info}" | "${sed}" -nr -e 's/$/,/' -e 's/, /,/g' -e 's/.*Flags:[[:space:]]*0x[0-9a-f]{1,}//p'`
- fi
- for n in $( "${readelf}" -d "${file}" \
- |"${grep}" -E '\(NEEDED\)' \
- |"${sed}" -r -e 's/^.*Shared library:[[:space:]]+\[([^]]+)\].*/\1/;' \
- ); do
- found=0
- for m in "${needed_list[@]}"; do
- [ "${n}" = "${m}" ] && found=1 && break
- done
- if [ ${found} -ne 0 ]; then
- do_trace "-> skipping already known dependency '%s'\n" "${n}"
- continue
- fi
- do_trace "-> handling new dependency '%s'\n" "${n}"
- needed_list+=( "${n}" )
- do_find_needed "${n}"
- do_trace "-> done handling dependency '%s'\n" "${n}"
- done
- search_rpath=( "${save_search_rpath[@]}" )
- do_trace "Finished parsing file '%s'\n" "${file}"
- }
- # Recursively scan a /etc/ld.so.conf file
- do_scan_etc_ldsoconf() {
- local ldsoconf="${1}"
- local g
- local f
- [ -f "${ldsoconf}" ] || return 0
- do_trace "Parsing ld.so.conf: '%s'\n" "${ldsoconf}"
- while read line; do
- case "${line}" in
- include\ *)
- g="${root}${line#include }"
- do_trace "-> handling include directive '%s'\n" "${g}"
- for f in ${g}; do
- do_scan_etc_ldsoconf "${f}"
- done
- do_trace "-> finished handling include directive '%s'\n" "${g}"
- ;;
- \#*|"")
- ;;
- *)
- do_trace "-> adding search dir '%s'\n" "${line}"
- needed_search_path+=( "${line}" )
- ;;
- esac
- done <"${ldsoconf}"
- do_trace "Finished parsing ld.so.conf: '%s'\n" "${ldsoconf}"
- }
- if [ -z "${1}" ]; then
- show_help
- elif [ ! -r "${1}" ]; then
- do_error "${1}: No such file or directory"
- fi
- # Build up the full list of search directories
- declare -a needed_search_path
- do_trace "Adding basic lib dirs\n"
- ld_library_path="${ld_library_path}:"
- while [ -n "${ld_library_path}" ]; do
- d="${ld_library_path%%:*}"
- if [ -n "${d}" ]; then
- do_trace "-> adding search dir '%s'\n" "${d}"
- needed_search_path+=( "${d}" )
- fi
- ld_library_path="${ld_library_path#*:}"
- done
- do_trace "Done adding basic lib dirs\n"
- do_trace "Scanning '/etc/ld.so.conf'\n"
- do_scan_etc_ldsoconf "${root}/etc/ld.so.conf"
- do_trace "Done scanning '/etc/ld.so.conf'\n"
- do_trace "Search path:\n"
- for p in "${needed_search_path[@]}"; do
- do_trace "-> '%s'\n" "${p}"
- done
- declare -a needed_list
- declare -a search_rpath
- do_trace "Scanning file '%s'\n" "${1}"
- do_process_file "${1}" initial
- do_trace "Done scanning file '%s'\n" "${1}"