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}"
|
---|