#!/bin/bash # extract-debuginfo.sh - automagically generate debug info # # Usage: extract-debuginfo.sh [--strict-build-id] [-g] [-r] # [builddir] # # The -g flag says to use strip -g instead of full strip on DSOs. # The --strict-build-id flag says to exit with failure status if # any ELF binary processed fails to contain a build-id note. # The -r flag says to use eu-strip --reloc-debug-sections. # # All file names in switches are relative to builddir (. if not given). # echo "Extracting debuginfo to /usr/lib/debug..." export LC_ALL=C # With -g arg, pass it to strip on libraries. strip_g=false # with -r arg, pass --reloc-debug-sections to eu-strip. strip_r=false # Barf on missing build IDs. strict=false BUILDDIR=. while [ $# -gt 0 ]; do case "${1}" in --sourcedir=*) SOURCEDIR=${1#--sourcedir=} ;; --buildroot=*) BUILDROOT=${1#--buildroot=} ;; --strict-build-id) strict=true ;; -g) strip_g=true ;; -r) strip_r=true ;; *) BUILDDIR=${1} shift break ;; esac shift done debugdir="${BUILDROOT}/usr/lib/debug" # A list of source files that are included in the -debuginfo packages. SOURCEFILE="$(mktemp)" strip_to_debug() { local g= local r= ${strip_r} && r=--reloc-debug-sections ${strip_g} && \ case "$(file -bi "${2}")" in application/x-sharedlib*) g=-g ;; esac eu-strip --remove-comment ${r} ${g} -f "${1}" "${2}" || exit chmod 444 "${1}" || exit } # Make a relative symlink to $1 called $3$2 shopt -s extglob link_relative() { local t="$1" f="$2" pfx="$3" local fn="${f#/}" tn="${t#/}" local fd td d while fd="${fn%%/*}"; td="${tn%%/*}"; [ "$fd" = "$td" ]; do fn="${fn#*/}" tn="${tn#*/}" done d="${fn%/*}" if [ "$d" != "$fn" ]; then d="${d//+([!\/])/..}" tn="${d}/${tn}" fi mkdir -p "$(dirname "$pfx$f")" && ln -snf "$tn" "$pfx$f" } # Make a symlink in /usr/lib/debug/$2 to $1 debug_link() { local l="/usr/lib/debug$2" local t="$1" link_relative "$t" "$l" "$BUILDROOT" } # Provide .2, .3, ... symlinks to all filename instances of this build-id. make_id_dup_link() { local id="${1}" file="${2}" idfile local n=1 while true; do idfile=".build-id/${id:0:2}/${id:2}.${n}" [ $# -eq 3 ] && idfile="${idfile}$3" if [ ! -L "${BUILDROOT}/usr/lib/debug/${idfile}" ]; then break fi n=$[${n}+1] done debug_link "${file}" "/${idfile}" } # Make a build-id symlink for id $1 with suffix $3 to file $2. make_id_link() { local id="${1}" file="${2}" local idfile=".build-id/${id:0:2}/${id:2}" [ $# -eq 3 ] && idfile="${idfile}${3}" local root_idfile="${BUILDROOT}/usr/lib/debug/${idfile}" if [ ! -L "${root_idfile}" ]; then debug_link "${file}" "/${idfile}" return fi make_id_dup_link "$@" [ $# -eq 3 ] && return 0 local other=$(readlink -m "${root_idfile}") other=${other#$BUILDROOT} if cmp -s "${root_idfile}" "${BUILDROOT}${file}" || eu-elfcmp -q "${root_idfile}" "${BUILDROOT}${file}" 2> /dev/null; then # Two copies. Maybe one has to be setuid or something. echo >&2 "*** WARNING: identical binaries are copied, not linked:" echo >&2 " ${file}" echo >&2 " and ${other}" else # This is pathological, break the build. echo >&2 "*** ERROR: same build ID in nonidentical files!" echo >&2 " ${file}" echo >&2 " and ${other}" exit 2 fi } get_debugfn() { dn=$(dirname "${1#$BUILDROOT}") bn=$(basename "$1" .debug).debug debugdn=${debugdir}${dn} debugfn=${debugdn}/${bn} } set -o pipefail strict_error=ERROR ${strict} || strict_error=WARNING # Strip ELF binaries find "$BUILDROOT" ! -path "${debugdir}/*.debug" -type f \ \( -perm -0100 -or -perm -0010 -or -perm -0001 \) \ -print | file -N -f - | sed -n -e 's/^\(.*\):[ ]*.*ELF.*, not stripped/\1/p' | xargs --no-run-if-empty stat -c '%h %D_%i %n' | while read nlinks inum f; do get_debugfn "${f}" [ -f "${debugfn}" ] && continue # If this file has multiple links, keep track and make # the corresponding .debug files all links to one file too. if [ ${nlinks} -gt 1 ]; then eval linked=\$linked_${inum} if [ -n "${linked}" ]; then eval id=\${linkedid_${inum}} make_id_dup_link "${id}" "${dn}/$(basename ${f})" make_id_dup_link "${id}" "/usr/lib/debug${dn}/${bn}" .debug link=${debugfn} get_debugfn "${linked}" echo " hard linked ${link} to ${debugfn}" mkdir -p "$(dirname "$link")" && ln -nf "$debugfn" "$link" continue else eval linked_${inum}=\${f} echo " file ${f} has $[${nlinks} - 1] other hard links" fi fi echo " Extracting debug info from ${f#${BUILDROOT}}" id=$(/usr/lib/pakfire/debugedit -i \ -b "${SOURCEDIR}" \ -d /usr/src/debug \ -l "${SOURCEFILE}" \ "${f}") || exit if [ ${nlinks} -gt 1 ]; then eval linkedid_${inum}=\${id} fi if [ -z "$id" ]; then echo >&2 "*** ${strict_error}: No build ID note found in ${f#${BUILDROOT}}" ${strict} && exit 2 fi [ -x /usr/bin/gdb-add-index ] && /usr/bin/gdb-add-index "${f}" > /dev/null 2>&1 # A binary already copied into /usr/lib/debug doesn't get stripped, # just has its file names collected and adjusted. case "${dn}" in /usr/lib/debug/*) [ -z "${id}" ] || make_id_link "${id}" "${dn}/$(basename ${f})" continue ;; esac mkdir -p "${debugdn}" if test -w "${f}"; then strip_to_debug "${debugfn}" "${f}" else chmod u+w "${f}" strip_to_debug "${debugfn}" "${f}" chmod u-w "${f}" fi if [ -n "${id}" ]; then make_id_link "${id}" "${dn}/$(basename ${f})" make_id_link "${id}" "/usr/lib/debug${dn}/${bn}" .debug fi done || exit # For each symlink whose target has a .debug file, # make a .debug symlink to that file. find $BUILDROOT ! -path "${debugdir}/*" -type l -print | while read f; do t=$(readlink -m "$f").debug f=${f#$BUILDROOT} t=${t#$BUILDROOT} if [ -f "$debugdir$t" ]; then echo "symlinked /usr/lib/debug$t to /usr/lib/debug${f}.debug" debug_link "/usr/lib/debug$t" "${f}.debug" fi done if [ -s "${SOURCEFILE}" ]; then mkdir -p "${BUILDROOT}/usr/src/debug" sort -z -u "${SOURCEFILE}" | grep -E -v -z '(|)$' | \ (cd "${SOURCEDIR}"; cpio -pd0mL "${BUILDROOT}/usr/src/debug" 2>/dev/null) # stupid cpio creates new directories in mode 0700, fixup find "${BUILDROOT}/usr/src/debug" -type d -print0 | \ xargs --no-run-if-empty -0 chmod a+rx # Fix ownership. chown root:root -R ${BUILDROOT}/usr/src/debug fi rm -f ${SOURCEFILE} exit 0