summaryrefslogtreecommitdiffstats
path: root/src/Daemon/abrt-debuginfo-install
blob: 6070b74d75428d9863f28e50097f425a32b7348c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
#!/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