d83c3e2c47f3b8fce4200d84d8d78c3c8f31f169
[rpm.git] / scripts / find-debuginfo.sh
1 #!/bin/bash
2 #find-debuginfo.sh - automagically generate debug info and file list
3 #for inclusion in an rpm spec file.
4 #
5 # Usage: find-debuginfo.sh [--strict-build-id] [-g] [-r] [-m] [-i]
6 #                          [-o debugfiles.list]
7 #                          [--run-dwz] [--dwz-low-mem-die-limit N]
8 #                          [--dwz-max-die-limit N]
9 #                          [--ver-rel VERSION-RELEASE]
10 #                          [[-l filelist]... [-p 'pattern'] -o debuginfo.list]
11 #                          [builddir]
12 #
13 # The -g flag says to use strip -g instead of full strip on DSOs or EXEs.
14 # The --strict-build-id flag says to exit with failure status if
15 # any ELF binary processed fails to contain a build-id note.
16 # The -r flag says to use eu-strip --reloc-debug-sections.
17 # The -m flag says to include a .gnu_debugdata section in the main binary.
18 # The -i flag says to include a .gdb_index section in the .debug file.
19 #
20 # A single -o switch before any -l or -p switches simply renames
21 # the primary output file from debugfiles.list to something else.
22 # A -o switch that follows a -p switch or some -l switches produces
23 # an additional output file with the debuginfo for the files in
24 # the -l filelist file, or whose names match the -p pattern.
25 # The -p argument is an grep -E -style regexp matching the a file name,
26 # and must not use anchors (^ or $).
27 #
28 # The --run-dwz flag instructs find-debuginfo.sh to run the dwz utility
29 # if available, and --dwz-low-mem-die-limit and --dwz-max-die-limit
30 # provide detailed limits.  See dwz(1) -l and -L option for details.
31 #
32 # If --ver-rel VERSION-RELEASE is given then debugedit is called to
33 # update the build-ids it finds adding the VERSION-RELEASE string as
34 # seed to recalculate the build-id hash.  This makes sure the
35 # build-ids in the ELF files are unique between versions and releases
36 # of the same package.
37 #
38 # All file names in switches are relative to builddir (. if not given).
39 #
40
41 # Figure out where we are installed so we can call other helper scripts.
42 lib_rpm_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
43
44 # With -g arg, pass it to strip on libraries or executables.
45 strip_g=false
46
47 # with -r arg, pass --reloc-debug-sections to eu-strip.
48 strip_r=false
49
50 # with -m arg, add minimal debuginfo to binary.
51 include_minidebug=false
52
53 # with -i arg, add GDB index to .debug file.
54 include_gdb_index=false
55
56 # Barf on missing build IDs.
57 strict=false
58
59 # DWZ parameters.
60 run_dwz=false
61 dwz_low_mem_die_limit=
62 dwz_max_die_limit=
63
64 # Version and release of the spec. Given by --ver-rel
65 ver_rel=
66
67 # Arch given by --unique-debug-arch
68 unique_debug_arch=
69
70 BUILDDIR=.
71 out=debugfiles.list
72 nout=0
73 while [ $# -gt 0 ]; do
74   case "$1" in
75   --strict-build-id)
76     strict=true
77     ;;
78   --run-dwz)
79     run_dwz=true
80     ;;
81   --dwz-low-mem-die-limit)
82     dwz_low_mem_die_limit=$2
83     shift
84     ;;
85   --dwz-max-die-limit)
86     dwz_max_die_limit=$2
87     shift
88     ;;
89   --ver-rel)
90     ver_rel=$2
91     shift
92     ;;
93   --unique-debug-arch)
94     unique_debug_arch=$2
95     shift
96     ;;
97   -g)
98     strip_g=true
99     ;;
100   -m)
101     include_minidebug=true
102     ;;
103   -i)
104     include_gdb_index=true
105     ;;
106   -o)
107     if [ -z "${lists[$nout]}" -a -z "${ptns[$nout]}" ]; then
108       out=$2
109     else
110       outs[$nout]=$2
111       ((nout++))
112     fi
113     shift
114     ;;
115   -l)
116     lists[$nout]="${lists[$nout]} $2"
117     shift
118     ;;
119   -p)
120     ptns[$nout]=$2
121     shift
122     ;;
123   -r)
124     strip_r=true
125     ;;
126   *)
127     BUILDDIR=$1
128     shift
129     break
130     ;;
131   esac
132   shift
133 done
134
135 if test -z "$ver_rel" -a -n "$unique_debug_arch"; then
136   echo >&2 "*** ERROR: --unique-debug-arch (${unique_debug_arch}) needs --ver-rel (${ver_rel})"
137   exit 2
138 fi
139
140 i=0
141 while ((i < nout)); do
142   outs[$i]="$BUILDDIR/${outs[$i]}"
143   l=''
144   for f in ${lists[$i]}; do
145     l="$l $BUILDDIR/$f"
146   done
147   lists[$i]=$l
148   ((++i))
149 done
150
151 LISTFILE="$BUILDDIR/$out"
152 SOURCEFILE="$BUILDDIR/debugsources.list"
153 LINKSFILE="$BUILDDIR/debuglinks.list"
154 ELFBINSFILE="$BUILDDIR/elfbins.list"
155
156 > "$SOURCEFILE"
157 > "$LISTFILE"
158 > "$LINKSFILE"
159 > "$ELFBINSFILE"
160
161 debugdir="${RPM_BUILD_ROOT}/usr/lib/debug"
162
163 strip_to_debug()
164 {
165   local g=
166   local r=
167   $strip_r && r=--reloc-debug-sections
168   $strip_g && case "$(file -bi "$2")" in
169   application/x-sharedlib*) g=-g ;;
170   application/x-executable*) g=-g ;;
171   esac
172   eu-strip --remove-comment $r $g -f "$1" "$2" || exit
173   chmod 444 "$1" || exit
174 }
175
176 add_minidebug()
177 {
178   local debuginfo="$1"
179   local binary="$2"
180
181   local dynsyms=`mktemp`
182   local funcsyms=`mktemp`
183   local keep_symbols=`mktemp`
184   local mini_debuginfo=`mktemp`
185
186   # Extract the dynamic symbols from the main binary, there is no need to also have these
187   # in the normal symbol table
188   nm -D "$binary" --format=posix --defined-only | awk '{ print $1 }' | sort > "$dynsyms"
189   # Extract all the text (i.e. function) symbols from the debuginfo
190   nm "$debuginfo" --format=posix --defined-only | awk '{ if ($2 == "T" || $2 == "t") print $1 }' | sort > "$funcsyms"
191   # Keep all the function symbols not already in the dynamic symbol table
192   comm -13 "$dynsyms" "$funcsyms" > "$keep_symbols"
193   # Copy the full debuginfo, keeping only a minumal set of symbols and removing some unnecessary sections
194   objcopy -S --remove-section .gdb_index --remove-section .comment --keep-symbols="$keep_symbols" "$debuginfo" "$mini_debuginfo" &> /dev/null
195   #Inject the compressed data into the .gnu_debugdata section of the original binary
196   xz "$mini_debuginfo"
197   mini_debuginfo="${mini_debuginfo}.xz"
198   objcopy --add-section .gnu_debugdata="$mini_debuginfo" "$binary"
199   rm -f "$dynsyms" "$funcsyms" "$keep_symbols" "$mini_debuginfo"
200 }
201
202 # Make a relative symlink to $1 called $3$2
203 shopt -s extglob
204 link_relative()
205 {
206   local t="$1" f="$2" pfx="$3"
207   local fn="${f#/}" tn="${t#/}"
208   local fd td d
209
210   while fd="${fn%%/*}"; td="${tn%%/*}"; [ "$fd" = "$td" ]; do
211     fn="${fn#*/}"
212     tn="${tn#*/}"
213   done
214
215   d="${fn%/*}"
216   if [ "$d" != "$fn" ]; then
217     d="${d//+([!\/])/..}"
218     tn="${d}/${tn}"
219   fi
220
221   mkdir -p "$(dirname "$pfx$f")" && ln -snf "$tn" "$pfx$f"
222 }
223
224 # Make a symlink in /usr/lib/debug/$2 to $1
225 debug_link()
226 {
227   local l="/usr/lib/debug$2"
228   local t="$1"
229   echo >> "$LINKSFILE" "$l $t"
230   link_relative "$t" "$l" "$RPM_BUILD_ROOT"
231 }
232
233 get_debugfn()
234 {
235   dn=$(dirname "${1#$RPM_BUILD_ROOT}")
236   if test -n "${unique_debug_arch}"; then
237     bn=$(basename "$1" .debug)-${ver_rel}.${unique_debug_arch}.debug
238   else
239     bn=$(basename "$1" .debug).debug
240   fi
241
242   debugdn=${debugdir}${dn}
243   debugfn=${debugdn}/${bn}
244 }
245
246 set -o pipefail
247
248 strict_error=ERROR
249 $strict || strict_error=WARNING
250
251 # Strip ELF binaries
252 find "$RPM_BUILD_ROOT" ! -path "${debugdir}/*.debug" -type f \
253                      \( -perm -0100 -or -perm -0010 -or -perm -0001 \) \
254                      -print |
255 file -N -f - | sed -n -e 's/^\(.*\):[   ]*.*ELF.*, not stripped.*/\1/p' |
256 xargs --no-run-if-empty stat -c '%h %D_%i %n' |
257 while read nlinks inum f; do
258   get_debugfn "$f"
259   [ -f "${debugfn}" ] && continue
260
261   # If this file has multiple links, keep track and make
262   # the corresponding .debug files all links to one file too.
263   if [ $nlinks -gt 1 ]; then
264     eval linked=\$linked_$inum
265     if [ -n "$linked" ]; then
266       eval id=\$linkedid_$inum
267       link=$debugfn
268       get_debugfn "$linked"
269       echo "hard linked $link to $debugfn"
270       mkdir -p "$(dirname "$link")" && ln -nf "$debugfn" "$link"
271       continue
272     else
273       eval linked_$inum=\$f
274       echo "file $f has $[$nlinks - 1] other hard links"
275     fi
276   fi
277
278   echo "extracting debug info from $f"
279   build_id_seed=
280   if [ ! -z "$ver_rel" ]; then
281     build_id_seed="--build-id-seed=$ver_rel"
282   fi
283   id=$(${lib_rpm_dir}/debugedit -b "$RPM_BUILD_DIR" -d /usr/src/debug \
284                               -i $build_id_seed -l "$SOURCEFILE" "$f") || exit
285   if [ $nlinks -gt 1 ]; then
286     eval linkedid_$inum=\$id
287   fi
288   if [ -z "$id" ]; then
289     echo >&2 "*** ${strict_error}: No build ID note found in $f"
290     $strict && exit 2
291   fi
292
293   # Add .gdb_index if requested.
294   if $include_gdb_index; then
295     if type gdb-add-index >/dev/null 2>&1; then
296       gdb-add-index "$f"
297     else
298       echo >&2 "*** ERROR: GDB index requested, but no gdb-add-index installed"
299       exit 2
300     fi
301   fi
302
303   # A binary already copied into /usr/lib/debug doesn't get stripped,
304   # just has its file names collected and adjusted.
305   case "$dn" in
306   /usr/lib/debug/*)
307     continue ;;
308   esac
309
310   mkdir -p "${debugdn}"
311   if test -w "$f"; then
312     strip_to_debug "${debugfn}" "$f"
313   else
314     chmod u+w "$f"
315     strip_to_debug "${debugfn}" "$f"
316     chmod u-w "$f"
317   fi
318
319   # strip -g implies we have full symtab, don't add mini symtab in that case.
320   $strip_g || ($include_minidebug && add_minidebug "${debugfn}" "$f")
321
322   echo "./${f#$RPM_BUILD_ROOT}" >> "$ELFBINSFILE"
323
324 done || exit
325
326 # Invoke the DWARF Compressor utility.
327 if $run_dwz \
328    && [ -d "${RPM_BUILD_ROOT}/usr/lib/debug" ]; then
329   dwz_files="`cd "${RPM_BUILD_ROOT}/usr/lib/debug"; find -type f -name \*.debug`"
330   if [ -n "${dwz_files}" ]; then
331     dwz_multifile_name="${RPM_PACKAGE_NAME}-${RPM_PACKAGE_VERSION}-${RPM_PACKAGE_RELEASE}.${RPM_ARCH}"
332     dwz_multifile_suffix=
333     dwz_multifile_idx=0
334     while [ -f "${RPM_BUILD_ROOT}/usr/lib/debug/.dwz/${dwz_multifile_name}${dwz_multifile_suffix}" ]; do
335       let ++dwz_multifile_idx
336       dwz_multifile_suffix=".${dwz_multifile_idx}"
337     done
338     dwz_multfile_name="${dwz_multifile_name}${dwz_multifile_suffix}"
339     dwz_opts="-h -q -r -m .dwz/${dwz_multifile_name}"
340     mkdir -p "${RPM_BUILD_ROOT}/usr/lib/debug/.dwz"
341     [ -n "${dwz_low_mem_die_limit}" ] \
342       && dwz_opts="${dwz_opts} -l ${dwz_low_mem_die_limit}"
343     [ -n "${dwz_max_die_limit}" ] \
344       && dwz_opts="${dwz_opts} -L ${dwz_max_die_limit}"
345     if type dwz >/dev/null 2>&1; then
346       ( cd "${RPM_BUILD_ROOT}/usr/lib/debug" && dwz $dwz_opts $dwz_files )
347     else
348       echo >&2 "*** ERROR: DWARF compression requested, but no dwz installed"
349       exit 2
350     fi
351     # Remove .dwz directory if empty
352     rmdir "${RPM_BUILD_ROOT}/usr/lib/debug/.dwz" 2>/dev/null
353     if [ -f "${RPM_BUILD_ROOT}/usr/lib/debug/.dwz/${dwz_multifile_name}" ]; then
354       id="`readelf -Wn "${RPM_BUILD_ROOT}/usr/lib/debug/.dwz/${dwz_multifile_name}" \
355              2>/dev/null | sed -n 's/^    Build ID: \([0-9a-f]\+\)/\1/p'`"
356     fi
357
358     # dwz invalidates .gnu_debuglink CRC32 in the main files.
359     cat "$ELFBINSFILE" |
360     (cd "$RPM_BUILD_ROOT"; \
361      xargs -d '\n' ${lib_rpm_dir}/sepdebugcrcfix usr/lib/debug)
362   fi
363 fi
364
365 # For each symlink whose target has a .debug file,
366 # make a .debug symlink to that file.
367 find "$RPM_BUILD_ROOT" ! -path "${debugdir}/*" -type l -print |
368 while read f
369 do
370   t=$(readlink -m "$f").debug
371   f=${f#$RPM_BUILD_ROOT}
372   t=${t#$RPM_BUILD_ROOT}
373   if [ -f "$debugdir$t" ]; then
374     echo "symlinked /usr/lib/debug$t to /usr/lib/debug${f}.debug"
375     debug_link "/usr/lib/debug$t" "${f}.debug"
376   fi
377 done
378
379 if [ -s "$SOURCEFILE" ]; then
380   mkdir -p "${RPM_BUILD_ROOT}/usr/src/debug"
381   LC_ALL=C sort -z -u "$SOURCEFILE" | grep -E -v -z '(<internal>|<built-in>)$' |
382   (cd "$RPM_BUILD_DIR"; cpio -pd0mL "${RPM_BUILD_ROOT}/usr/src/debug")
383   # stupid cpio creates new directories in mode 0700, fixup
384   find "${RPM_BUILD_ROOT}/usr/src/debug" -type d -print0 |
385   xargs --no-run-if-empty -0 chmod a+rx
386 fi
387
388 if [ -d "${RPM_BUILD_ROOT}/usr/lib" -o -d "${RPM_BUILD_ROOT}/usr/src" ]; then
389   ((nout > 0)) ||
390   test ! -d "${RPM_BUILD_ROOT}/usr/lib" ||
391   (cd "${RPM_BUILD_ROOT}/usr/lib"; find debug -type d) |
392   sed 's,^,%dir /usr/lib/,' >> "$LISTFILE"
393
394   (cd "${RPM_BUILD_ROOT}/usr"
395    test ! -d lib/debug || find lib/debug ! -type d
396    test ! -d src/debug || find src/debug -mindepth 1 -maxdepth 1
397   ) | sed 's,^,/usr/,' >> "$LISTFILE"
398 fi
399
400 # Append to $1 only the lines from stdin not already in the file.
401 append_uniq()
402 {
403   grep -F -f "$1" -x -v >> "$1"
404 }
405
406 # Helper to generate list of corresponding .debug files from a file list.
407 filelist_debugfiles()
408 {
409   local extra="$1"
410   shift
411   sed 's/^%[a-z0-9_][a-z0-9_]*([^)]*) *//
412 s/^%[a-z0-9_][a-z0-9_]* *//
413 /^$/d
414 '"$extra" "$@"
415 }
416
417 # Write an output debuginfo file list based on given input file lists.
418 filtered_list()
419 {
420   local out="$1"
421   shift
422   test $# -gt 0 || return
423   grep -F -f <(filelist_debugfiles 's,^.*$,/usr/lib/debug&.debug,' "$@") \
424         -x $LISTFILE >> $out
425   sed -n -f <(filelist_debugfiles 's/[\\.*+#]/\\&/g
426 h
427 s,^.*$,s# &$##p,p
428 g
429 s,^.*$,s# /usr/lib/debug&.debug$##p,p
430 ' "$@") "$LINKSFILE" | append_uniq "$out"
431 }
432
433 # Write an output debuginfo file list based on an grep -E -style regexp.
434 pattern_list()
435 {
436   local out="$1" ptn="$2"
437   test -n "$ptn" || return
438   grep -E -x -e "$ptn" "$LISTFILE" >> "$out"
439   sed -n -r "\#^$ptn #s/ .*\$//p" "$LINKSFILE" | append_uniq "$out"
440 }
441
442 #
443 # When given multiple -o switches, split up the output as directed.
444 #
445 i=0
446 while ((i < nout)); do
447   > ${outs[$i]}
448   filtered_list ${outs[$i]} ${lists[$i]}
449   pattern_list ${outs[$i]} "${ptns[$i]}"
450   grep -Fvx -f ${outs[$i]} "$LISTFILE" > "${LISTFILE}.new"
451   mv "${LISTFILE}.new" "$LISTFILE"
452   ((++i))
453 done
454 if ((nout > 0)); then
455   # Now add the right %dir lines to each output list.
456   (cd "${RPM_BUILD_ROOT}"; find usr/lib/debug -type d) |
457   sed 's#^.*$#\\@^/&/@{h;s@^.*$@%dir /&@p;g;}#' |
458   LC_ALL=C sort -ur > "${LISTFILE}.dirs.sed"
459   i=0
460   while ((i < nout)); do
461     sed -n -f "${LISTFILE}.dirs.sed" "${outs[$i]}" | sort -u > "${outs[$i]}.new"
462     cat "${outs[$i]}" >> "${outs[$i]}.new"
463     mv -f "${outs[$i]}.new" "${outs[$i]}"
464     ((++i))
465   done
466   sed -n -f "${LISTFILE}.dirs.sed" "${LISTFILE}" | sort -u > "${LISTFILE}.new"
467   cat "$LISTFILE" >> "${LISTFILE}.new"
468   mv "${LISTFILE}.new" "$LISTFILE"
469 fi