xref: /openbmc/openbmc-tools/ipkdbg/ipkdbg.in (revision 4ffeec6f)
1#!/bin/sh
2
3set -eu
4
5: ${IPKDBG_OPKG_CACHE:=}
6: ${IPKDBG_CONF_HOST:=host.local}
7: ${IPKDBG_CONF_MNT:=mountpoint}
8: ${IPKDBG_CONF_LOC:=themoon}
9: ${IPKDBG_CONF_ROOT:=path}
10: ${IPKDBG_CONF_USER:=$USER}
11: ${IPKDBG_WGET_OPTS:="--quiet"}
12: ${IPKDBG_ZSTD:=zstd}
13
14ipkdbg_error() {
15    /bin/echo -e "$@" | fold >&2
16}
17
18ipkdbg_info() {
19    /bin/echo -e "$@" | fold
20}
21
22ipkdbg_help() {
23/bin/echo -e "\033[1mNAME\033[0m"
24/bin/echo -e "\tipkdbg - debug OpenBMC applications from an (internally) released firmware"
25/bin/echo -e
26/bin/echo -e "\033[1mSYNOPSIS\033[0m"
27/bin/echo -e "\tipkdbg [-q] RELEASE FILE CORE [PACKAGE...]"
28/bin/echo -e
29/bin/echo -e "\033[1mDESCRIPTION\033[0m"
30/bin/echo -e "\tRELEASE is the firmware release whose packages to install"
31/bin/echo -e "\tFILE is the absolute path to the binary of interest in the target environment"
32/bin/echo -e "\tCORE is an optional core file generated by FILE. Pass '-' for no core file"
33/bin/echo -e "\tPACKAGES will be used to populate a temporary rootfs for debugging FILE"
34/bin/echo -e
35/bin/echo -e "\033[1mOPTIONS\033[0m"
36/bin/echo -e "\t\033[1m-h\033[0m"
37/bin/echo -e "\tPrint this help."
38/bin/echo -e
39/bin/echo -e "\t\033[1m-q\033[0m"
40/bin/echo -e "\tQuit gdb once done. Intended for use in a scripting environment in combination"
41/bin/echo -e "\twith a core file, as the backtrace will be printed as an implicit first command."
42/bin/echo -e
43/bin/echo -e "\033[1mENVIRONMENT\033[0m"
44/bin/echo -e "\tThere are several important environment variables controlling the behaviour of"
45/bin/echo -e "\tthe script:"
46/bin/echo -e
47/bin/echo -e "\t\033[1mIPKDBG_OPKG_CACHE\033[0m"
48/bin/echo -e "\tA package cache directory for opkg. Defaults to empty, disabling the cache."
49/bin/echo -e
50/bin/echo -e "\t\033[1mIPKDBG_CONF_HOST\033[0m"
51/bin/echo -e "\tHostname for access to opkg.conf over the web interface"
52/bin/echo -e
53/bin/echo -e "\tDefaults to '${IPKDBG_CONF_HOST}'"
54/bin/echo -e
55/bin/echo -e "\t\033[1mIPKDBG_CONF_MNT\033[0m"
56/bin/echo -e "\tMount-point for access to opkg.conf"
57/bin/echo -e
58/bin/echo -e "\tDefaults to '${IPKDBG_CONF_MNT}'"
59/bin/echo -e
60/bin/echo -e "\t\033[1mIPKDBG_CONF_LOC\033[0m"
61/bin/echo -e "\tGeo-location for access to opkg.conf"
62/bin/echo -e
63/bin/echo -e "\tDefaults to '${IPKDBG_CONF_LOC}'"
64/bin/echo -e
65/bin/echo -e "\t\033[1mIPKDBG_CONF_ROOT\033[0m"
66/bin/echo -e "\tPath to the directory containing build artifacts, for access to opkg.conf"
67/bin/echo -e
68/bin/echo -e "\tDefaults to '${IPKDBG_CONF_ROOT}'"
69/bin/echo -e
70/bin/echo -e "\t\033[1mIPKDBG_CONF_USER\033[0m"
71/bin/echo -e "\tUsername for access to opkg.conf over the web interface"
72/bin/echo -e
73/bin/echo -e "\tDefaults to \$USER ($USER)"
74/bin/echo -e
75/bin/echo -e "\t\033[1mIPKDBG_GDB\033[0m"
76/bin/echo -e "\tThe gdb(1) binary to invoke. Automatically detected if unset."
77/bin/echo -e
78/bin/echo -e "\t\033[1mIPKDBG_WGET_OPTS\033[0m"
79/bin/echo -e "\tUser options to pass to wget(1) when fetching opkg.conf. Defaults to"
80/bin/echo -e "\t'$IPKDBG_WGET_OPTS'"
81/bin/echo -e
82/bin/echo -e "\t\033[1mIPKDBG_ZSTD\033[0m"
83/bin/echo -e "\tThe zstd(1) binary to extract the compressed core dump. Automatically"
84/bin/echo -e "\tdetected if unset."
85/bin/echo -e
86/bin/echo -e "\033[1mEXAMPLE\033[0m"
87/bin/echo -e "\tipkdbg 1020.2206.20220208a \\"
88/bin/echo -e "\t\t/usr/bin/nvmesensor - \\"
89/bin/echo -e "\t\tdbus-sensors dbus-sensors-dbg"
90}
91
92IPKDBG_OPT_QUIT=0
93
94while getopts hq f
95do
96    case $f in
97    q) IPKDBG_OPT_QUIT=1;;
98    h|\?) ipkdbg_help ; exit 1;;
99    esac
100done
101shift $(expr $OPTIND - 1)
102
103trap ipkdbg_help EXIT
104
105ipkdbg_core_extract()
106{
107    if [ "-" = "$1" ]
108    then
109        echo -
110    else
111        local src="$(realpath "$1")"
112        local dst="${src%.zst}"
113
114        command -v $IPKDBG_ZSTD > /dev/null
115        $IPKDBG_ZSTD --decompress --quiet --quiet --force -o "$dst" "$src" || true
116        echo "$dst"
117    fi
118}
119
120IPKDBG_BUILD=$1; shift
121IPKDBG_FILE=$1; shift
122IPKDBG_CORE=$(ipkdbg_core_extract "$1"); shift
123IPKDBG_PKGS=$@
124
125: ${IPKDBG_GDB:=}
126if [ -n "$IPKDBG_GDB" ]
127then
128    ipkdbg_info "Using provided gdb command '$IPKDBG_GDB'"
129else
130    os_id=$(. /etc/os-release; echo ${ID}-${VERSION_ID})
131    case $os_id in
132    rhel-8.6 | fedora*)
133        IPKDBG_GDB=gdb
134        if [ -z "$(command -v $IPKDBG_GDB)" ]
135        then
136            ipkdbg_error "Please install the gdb package:"
137            ipkdbg_error
138            ipkdbg_error "\tsudo dnf install gdb"
139            ipkdbg_error
140            exit 1
141        fi
142        ;;
143    rhel*)
144        IPKDBG_GDB=gdb-multiarch
145        if [ -z "$(command -v $IPKDBG_GDB)" ]
146        then
147            ipkdbg_error "Please install the gdb-multiarch package:"
148            ipkdbg_error
149            ipkdbg_error "\tsudo dnf install gdb-multiarch"
150            ipkdbg_error
151            exit 1
152        fi
153        ;;
154    ubuntu*)
155        IPKDBG_GDB=gdb-multiarch
156        if [ -z "$(command -v $IPKDBG_GDB)" ]
157        then
158            ipkdbg_error "Please Install the gdb-multiarch package"
159            ipkdbg_error
160            ipkdbg_error "\tsudo apt install gdb-multiarch"
161            ipkdbg_error
162            exit 1
163        fi
164        ;;
165    *)
166        ipkdbg_error "Unrecognised distribution $release_id. Please set IPKDBG_GDB or " \
167            "install an appropriate gdb binary to invoke"
168        exit 1
169        ;;
170    esac
171    ipkdbg_info "Using gdb command ${IPKDBG_GDB} ($(command -v $IPKDBG_GDB))"
172fi
173
174ipkdbg_archive_extract() {
175    local offset=$1
176    local work=$2
177    tail -n+$offset $0 | base64 --decode - | tar -xz -C $work
178}
179
180ipkdbg_opkg_path() {
181    local root=$1
182    local arch=$(uname -m)
183    local release_id=$(. /etc/os-release; echo $ID)
184    local release_version_id=$(. /etc/os-release; echo $VERSION_ID)
185    local p=${root}/bin/${arch}/${release_id}/${release_version_id}/opkg
186    if [ ! -x "$p" ]
187    then
188        ipkdbg_error "Unsupported environment:"
189        ipkdbg_error
190        ipkdbg_error "Architecture:\t$arch"
191        ipkdbg_error "Distribution ID:\t$release_id"
192        ipkdbg_error "Distribution Version:\t$release_version_id"
193        exit 1
194    fi
195    echo $p
196}
197
198if [ ! -f $0 ]
199then
200    ipkdbg_error "Please execute the script with a relative or absolute path"
201    exit 1
202fi
203
204IPKDBG_DATA=$(awk '/^__ARCHIVE_BEGIN__$/ { print NR + 1; exit 0 }' $0)
205IPKDBG_WORK=$(mktemp -t --directory ipkdbg.XXX)
206IPKDBG_BINS=${IPKDBG_WORK}/tools
207IPKDBG_ROOT=${IPKDBG_WORK}/root
208IPKDBG_CONF=${IPKDBG_WORK}/opkg.conf
209IPKDBG_DB=${IPKDBG_WORK}/database
210
211cleanup() {
212    rm -rf $IPKDBG_WORK
213}
214
215trap cleanup EXIT INT QUIT KILL
216
217mkdir $IPKDBG_BINS $IPKDBG_DB
218ipkdbg_archive_extract $IPKDBG_DATA $IPKDBG_BINS
219
220IPKDBG_OPKG_BIN=$(ipkdbg_opkg_path $IPKDBG_BINS)
221
222ipkdbg_build_gen_path() {
223    local build=$1
224    local component="$2"
225    echo /${IPKDBG_CONF_MNT}/${IPKDBG_CONF_LOC}/${IPKDBG_CONF_ROOT}/${build}/"$component"
226}
227
228ipkdbg_build_gen_url() {
229    local build=$1
230    local component="$2"
231    echo https://${IPKDBG_CONF_HOST}/${IPKDBG_CONF_MNT}/${IPKDBG_CONF_LOC}/${IPKDBG_CONF_ROOT}/${build}/${component}
232}
233
234ipkdbg_build_gen_cache() {
235    local build=$1
236    local component="$2"
237    echo "${HOME}/.cache/ipkdbg/builds/${build}/${component}"
238}
239
240ipkdbg_opkg_conf_gen_path() {
241    local build=$1
242    ipkdbg_build_gen_path $build bmc_ipk/opkg.conf
243}
244
245ipkdbg_opkg_fetch_path() {
246    local path=$1
247    local output=$2
248    cp "$path" "$output" > /dev/null 2>&1
249}
250
251ipkdbg_opkg_conf_gen_url() {
252    local build=$1
253    ipkdbg_build_gen_url $build bmc_ipk/opkg.conf
254}
255
256ipkdbg_opkg_fetch_url() {
257    local url=$1
258    local output=$2
259    # We don't want URL to wrap
260    ipkdbg_info "Authenticating as user $IPKDBG_CONF_USER"
261    if ! wget --http-user=$IPKDBG_CONF_USER \
262        --ask-password \
263        --output-document $output \
264        $IPKDBG_WGET_OPTS \
265        $url
266    then
267        ipkdbg_error "Failed to fetch resource"
268        exit 1
269    fi
270}
271
272ipkdbg_opkg_conf_gen_cache() {
273    local build=$1
274    ipkdbg_build_gen_cache $build opkg.conf
275}
276
277ipkdbg_opkg_conf_fetch_cache() {
278    local build=$1
279    local output=$2
280    local path="$(ipkdbg_opkg_conf_gen_cache $build)"
281    cp "$path" "$output" > /dev/null 2>&1
282}
283
284ipkdbg_opkg_conf_install() {
285    local build=$1
286    local output=$2
287    mkdir -p $(dirname $output)
288    if ! ipkdbg_opkg_conf_fetch_cache $build $output
289    then
290        local cache="$(ipkdbg_opkg_conf_gen_cache $build)"
291        mkdir -p $(dirname $cache)
292        url=
293        ipkdbg_opkg_fetch_path "$(ipkdbg_opkg_conf_gen_path $build)" $cache ||
294            (echo "Configuring opkg via $(ipkdbg_opkg_conf_gen_url $build)" &&
295                ipkdbg_opkg_fetch_url "$(ipkdbg_opkg_conf_gen_url $build)" $cache)
296        ipkdbg_opkg_conf_fetch_cache $build $output
297    fi
298}
299
300ipkdbg_opkg_db_gen_path() {
301    local build=$1
302    ipkdbg_build_gen_path $build bmc_ipk/opkg-database.tar.xz
303}
304
305ipkdbg_opkg_db_gen_url() {
306    local build=$1
307    ipkdbg_build_gen_url ${build} bmc_ipk/opkg-database.tar.xz
308}
309
310ipkdbg_opkg_db_gen_cache() {
311    local build=$1
312    ipkdbg_build_gen_cache $build opkg-database.tar.xz
313}
314
315ipkdbg_opkg_db_install() {
316    local build=$1
317    local root=$2
318    local state=${root}/var/lib/opkg
319    local cache="$(ipkdbg_opkg_db_gen_cache $build)"
320    mkdir -p $state
321    if ! [ -f $cache ]
322    then
323        mkdir -p $(dirname $cache)
324        ipkdbg_opkg_fetch_path "$(ipkdbg_opkg_db_gen_path $build)" $cache ||
325            ipkdbg_opkg_fetch_url "$(ipkdbg_opkg_db_gen_url $build)" $cache ||
326            rm -f $cache
327    fi
328    tar -xf $cache -C $state 2> /dev/null
329    mkdir -p ${root}/usr/local
330    ln -s ${root}/var ${root}/usr/local/var
331}
332
333ipkdbg_opkg() {
334    $IPKDBG_OPKG_BIN \
335        $([ -z "$IPKDBG_OPKG_CACHE" ] ||
336            echo --cache-dir $IPKDBG_OPKG_CACHE --host-cache-dir) \
337        -V1 -f $IPKDBG_CONF -o $IPKDBG_ROOT $@
338}
339
340ipkdbg_gdb_extract_bin() {
341    local core=$1
342    $IPKDBG_GDB --core $core -ex quit 2> /dev/null |
343        awk -F "[\`']" '/Core was generated by/ { print $2 }' |
344        awk -F " " '{ print $1 }' # Chop off the arguments, we only want the binary path
345}
346
347ipkdbg_opkg_find() {
348    ipkdbg_opkg find $@ | awk '{ print $1 }'
349}
350
351ipkdbg_opkg_find_extra() {
352    local pkg=$1
353
354    # Try appending -dbg and -src to the binary package name
355    extra_pkgs="$(ipkdbg_opkg_find ${pkg}-dbg) $(ipkdbg_opkg_find ${pkg}-src)"
356
357    # If that fails, we probably have a split binary package
358    if [ -z "$extra_pkgs" ]
359    then
360        # Strip the last component off as it's probably the split binary package name and
361        # try again
362        extra_pkgs="$(ipkdbg_opkg_find ${pkg%-*}-dbg) $(ipkdbg_opkg_find ${pkg%-*}-src)"
363    fi
364    echo $extra_pkgs
365}
366
367ipkdbg_opkg_conf_install $IPKDBG_BUILD $IPKDBG_CONF
368
369# Extract the binary path from the core
370if [ '-' = "$IPKDBG_FILE" -a '-' != "$IPKDBG_CORE" ]
371then
372    IPKDBG_FILE=$(ipkdbg_gdb_extract_bin $IPKDBG_CORE)
373fi
374
375# Update the package database before potentially looking up the debug packages
376ipkdbg_opkg update
377
378# Extract the package name for the binary
379if [ '-' != "$IPKDBG_CORE" ]
380then
381    if ipkdbg_opkg_db_install $IPKDBG_BUILD $IPKDBG_DB
382    then
383        # Look up the package for the binary
384        IPKDBG_CORE_PKG="$(IPKDBG_ROOT=$IPKDBG_DB ipkdbg_opkg search ${IPKDBG_DB}${IPKDBG_FILE} | awk '{ print $1 }')"
385        if [ -n "$IPKDBG_CORE_PKG" ]
386        then
387            # Look up the extra (debug, source) packages for the binary package
388            IPKDBG_PKGS="$IPKDBG_PKGS $IPKDBG_CORE_PKG"
389            IPKDBG_PKGS="$IPKDBG_PKGS $(ipkdbg_opkg_find_extra $IPKDBG_CORE_PKG)"
390        fi
391    fi
392
393    if [ -z "$IPKDBG_PKGS" ]
394    then
395        ipkdbg_error "Unable to determine package-set to install, please specify" \
396                 "appropriate packages on the command line"
397        exit 1
398    fi
399fi
400
401# Force installation of gcc-runtime-dbg to give us debug symbols for libstdc++
402IPKDBG_PKGS="gcc-runtime-dbg $IPKDBG_PKGS"
403
404if [ -n "$IPKDBG_OPKG_CACHE" ]
405then
406    mkdir -p "$IPKDBG_OPKG_CACHE"
407    ipkdbg_opkg install --download-only $IPKDBG_PKGS
408fi
409
410ipkdbg_opkg install $IPKDBG_PKGS | grep -vF 'Warning when extracting archive entry'
411
412cat <<EOF > ${IPKDBG_BINS}/opkg
413#!/bin/sh
414exec $IPKDBG_OPKG_BIN -f $IPKDBG_CONF -o $IPKDBG_ROOT \$@
415EOF
416chmod +x ${IPKDBG_BINS}/opkg
417
418PATH=${IPKDBG_BINS}:${PATH} $IPKDBG_GDB -q \
419    -iex "set solib-absolute-prefix $IPKDBG_ROOT" \
420    -iex "add-auto-load-safe-path $IPKDBG_ROOT" \
421    -iex "set directories $IPKDBG_ROOT" \
422    -iex "cd $IPKDBG_ROOT" \
423    $([ '-' = "$IPKDBG_CORE" ] || echo -ex bt) \
424    $([ 0 -eq $IPKDBG_OPT_QUIT ] || echo -ex quit) \
425    ${IPKDBG_ROOT}${IPKDBG_FILE} \
426    $([ '-' = "$IPKDBG_CORE" ] || echo $IPKDBG_CORE)
427
428exit 0
429
430__ARCHIVE_BEGIN__
431