#!/bin/sh # Called by abrtd before producing a backtrace. # The task of this script is to install debuginfos. # # Just using [pk-]debuginfo-install does not work well. # - they can't install more than one version of debuginfo # for a package # - their output is unsuitable for scripting # - debuginfo-install aborts if yum lock is busy # - pk-debuginfo-install was observed to hang # # Usage: abrt-debuginfo-install CORE TEMPDIR [CACHEDIR] # If CACHEDIR is specified, debuginfos should be installed there. # If not, debuginfos should be installed into TEMPDIR. # # Currently, we are called with CACHEDIR set to "/var/cache/abrt-di", # but in the future it may be omitted or set to something else. # Script must be ready for those cases too. Consider, for example, # corner cases of "" and "/". # # Output goes to GUI as debuginfo install log. The script should be careful # to give useful, but not overly cluttered info to stdout. # Additionally, abrt daemon handles "MISSING:xxxx" messages specially: # xxxx will be prepended to backtrace. This is used to inform about # missing debuginfos. # # Exitcodes: # 0 - all debuginfos are installed # 1 - not all debuginfos are installed # 2+ - serious problem # # Algorithm: # - Create TEMPDIR # - Extract build-ids from coredump # - For every build-id, check /usr/lib/debug/.build-id/XX/XXXX.debug # and CACHEDIR/usr/lib/debug/.build-id/XX/XXXX.debug # - If they all exist, exit 0 # - Using "yum provides /usr/lib/debug/.build-id/XX/XXXX.debug", # figure out which debuginfo packages are needed # - Download them using "yumdownloader PACKAGE..." # - Unpack them with rpm2cpio | cpio to TEMPDIR # - If CACHEDIR is specified, copy usr/lib/debug/.build-id/XX/XXXX.debug # to CACHEDIR/usr/lib/debug/.build-id/XX/XXXX.debug and delete TEMPDIR # - Report which XX/XXXX.debug are still missing. # # For better debuggability, eu_unstrip.OUT, yum_provides.OUT etc files # are saved in TEMPDIR, and TEMPDIR is not deleted if we exit with exitcode 2 # ("serious problem"). # # In the future, we may want to use a separate CACHEDIR (say, /var/cache/abrt-di) # and use it with this gdb command: # set debug-file-directory /usr/lib/debug/.build-id:CACHEDIR/usr/lib/debug/.build-id # but current gdb can't handle DIR1:DIR2. # So, currently we are called with CACHEDIR set to "/", and don't pass # "set debug-file-directory" to gdb. # This is ugly, since it messes up /usr/lib/debug/.build-id over time # by piling up debuginfos there without any means to control their amount, # but it's the only way to make it work with current gdb. core=$1 tempdir=$2 cachedir=$3 debug=false exec 2>&1 test -f "$core" || exit 2 # cachedir is optional test x"$cachedir" = x"" || test -d "$cachedir" || exit 2 # tempdir must not exist test -e "$tempdir" && exit 2 mkdir -- "$tempdir" || exit 2 cd "$tempdir" || exit 2 $debug && echo "Installing rpms to $tempdir" count_words() { echo $# } cleanup_and_report_missing() { # Which debuginfo files are still missing, including those we just unpacked? missing_build_ids=`for build_id in $build_ids; do build_id1=${build_id:0:2} build_id2=${build_id:2} file="usr/lib/debug/.build-id/$build_id1/$build_id2.debug" test -f "/$file" && continue test -f "$cachedir/$file" && continue echo -n "$build_id " done` $debug && echo "missing_build_ids:$missing_build_ids" # If cachedir is specified, tempdir is just a staging area. Delete it if test x"$cachedir" != x""; then $debug && echo "Removing $tempdir" rm -rf "$tempdir" fi for missing in $missing_build_ids; do echo "MISSING:$missing" done test x"$missing_build_ids" != x"" && echo "`count_words $missing_build_ids` debuginfos can't be found" } # eu-unstrip output example: # 0x400000+0x209000 23c77451cf6adff77fc1f5ee2a01d75de6511dda@0x40024c - - [exe] # or # 0x400000+0x20d000 233aa1a57e9ffda65f53efdaf5e5058657a39993@0x40024c /usr/libexec/im-settings-daemon /usr/lib/debug/usr/libexec/im-settings-daemon.debug [exe] # 0x7fff5cdff000+0x1000 0d3eb4326fd7489fcf9b598269f1edc420e2c560@0x7fff5cdff2f8 . - linux-vdso.so.1 # 0x3d15600000+0x208000 20196628d1bc062279622615cc9955554e5bb227@0x3d156001a0 /usr/lib64/libnotify.so.1.1.3 /usr/lib/debug/usr/lib64/libnotify.so.1.1.3.debug libnotify.so.1 # 0x7fd8ae931000+0x62d000 dd49f44f958b5a11a1635523b2f09cb2e45c1734@0x7fd8ae9311a0 /usr/lib64/libgtk-x11-2.0.so.0.1600.6 /usr/lib/debug/usr/lib64/libgtk-x11-2.0.so.0.1600.6.debug echo "Getting list of build IDs" # Observed errors: # eu-unstrip: /var/cache/abrt/ccpp-1256301004-2754/coredump: Callback returned failure eu_unstrip_OUT=`eu-unstrip "--core=$core" -n 2>eu_unstrip.ERR` err=$? printf "%s\nexitcode:%s\n" "$eu_unstrip_OUT" $err >eu_unstrip.OUT test $err = 0 || exit 2 # Get space-separated list of all build-ids # There can be duplicates (observed in real world) build_ids=`printf "%s\n" "$eu_unstrip_OUT" \ | while read junk1 build_id binary_file di_file lib_name junk2; do build_id=${build_id%%@*} # This filters out linux-vdso.so, among others test x"$lib_name" != x"[exe]" && test x"${binary_file:0:1}" != x"/" && continue # Sanitize build_id: must be longer than 2 chars test ${#build_id} -le 2 && continue # Sanitize build_id: must have only hex digits test x"${build_id//[0-9a-f]/}" != x"" && continue echo "$build_id" done | sort | uniq | xargs` $debug && echo "build_ids:$build_ids" # Which debuginfo files are missing? missing_debuginfo_files=`for build_id in $build_ids; do build_id1=${build_id:0:2} build_id2=${build_id:2} file="usr/lib/debug/.build-id/$build_id1/$build_id2.debug" if test x"$cachedir" != x"" && test x"$cachedir" != x"/" ; then test -f "$cachedir/$file" && continue fi test -f "/$file" && continue echo -n "/$file " done` $debug && echo "missing_debuginfo_files:$missing_debuginfo_files" if test x"$missing_debuginfo_files" = x""; then cleanup_and_report_missing exit 0 fi # We'll run something like: # yum --enablerepo='*debuginfo*' --quiet provides \ # /usr/lib/debug/.build-id/bb/11528d59940983f495e9cb099cafb0cb206051.debug \ # /usr/lib/debug/.build-id/c5/b84c0ad3676509dc30bfa7d42191574dac5b06.debug ... echo "Determining list of packages for `count_words $missing_debuginfo_files` missing debuginfos" yum_provides_OUT=`yum --enablerepo='*debuginfo*' --quiet provides $missing_debuginfo_files 2>&1` err=$? printf "%s\nexitcode:%s\n" "$yum_provides_OUT" $err >yum_provides.OUT test $err = 0 || exit 2 # The output is pretty machine-unfriendly: # glibc-debuginfo-2.10.90-24.x86_64 : Debug information for package glibc # Repo : rawhide-debuginfo # Matched from: # Filename : /usr/lib/debug/.build-id/5b/c784c8d63f87dbdeb747a773940956a18ecd2f.debug # # 1:dbus-debuginfo-1.2.12-2.fc11.x86_64 : Debug information for package dbus # Repo : updates-debuginfo # Matched from: # Filename : /usr/lib/debug/.build-id/bc/da7d09eb6c9ee380dae0ed3d591d4311decc31.debug # Need to massage it a lot. # There can be duplicates (one package may provide many debuginfos). packages=`printf "%s\n" "$yum_provides_OUT" \ | grep -- -debuginfo- \ | sed 's/^[0-9]*://' \ | sed -e 's/ .*//' -e 's/:.*//' \ | sort | uniq | xargs` $debug && echo "packages:$packages" # yum may return "" here if it found no packages (say, if coredump is from a new, # unreleased package fresh from koji). if test x"$packages" = x""; then cleanup_and_report_missing exit 1 fi num_packages=`count_words $packages` echo "Downloading $num_packages packages" ## Download with one command (too silent): ## Redirecting, since progress bar stuff only messes up our output ##yumdownloader --enablerepo='*debuginfo*' --quiet $packages >yumdownloader.OUT 2>&1 ##err=$? ##echo "exitcode:$err" >>yumdownloader.OUT ##test $err = 0 || exit 2 >yumdownloader.OUT i=1 for pkg in $packages; do echo "Download $i/$num_packages: $pkg" echo "Download $i/$num_packages: $pkg" >>yumdownloader.OUT yumdownloader --enablerepo='*debuginfo*' --quiet $pkg >>yumdownloader.OUT 2>&1 err=$? echo "exitcode:$err" >>yumdownloader.OUT echo >>yumdownloader.OUT test $err = 0 || { echo "Download of $pkg failed!"; sleep 1; } : $((i++)) done for f in *.rpm; do # Happens if no .rpm's were downloaded (yumdownloader problem) # In this case, $f is the literal "*.rpm" string test -f "$f" || exit 2 echo "Unpacking: $f" echo "Processing: $f" >>unpack.OUT rpm2cpio <"$f" 2>>unpack.OUT | cpio -id >>unpack.OUT 2>&1 done # Copy debuginfo files to cachedir if test x"$cachedir" != x"" && test -d "$cachedir"; then for build_id in $build_ids; do build_id1=${build_id:0:2} build_id2=${build_id:2} file="usr/lib/debug/.build-id/$build_id1/$build_id2.debug" test -f "/$file" && continue test x"$cachedir" != x"/" && test -f "$cachedir/$file" && continue if test -f "$file"; then # file is one of those we just installed. # Cache it if cachedir is specified. mkdir -p "$cachedir/usr/lib/debug/.build-id/$build_id1" # Note: this does not preserve symlinks. This is intentional $debug && echo Copying2 "$file" to "$cachedir/$file" >&2 cp --remove-destination "$file" "$cachedir/$file" continue fi done fi $debug && echo "missing_build_ids:$missing_build_ids" cleanup_and_report_missing test x"$missing_build_ids" != x"" && exit 1 exit 0