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