#!/bin/sh set -eu : ${IPKDBG_OPKG_CACHE:=} : ${IPKDBG_CONF_HOST:=host.local} : ${IPKDBG_CONF_MNT:=mountpoint} : ${IPKDBG_CONF_LOC:=themoon} : ${IPKDBG_CONF_ROOT:=path} : ${IPKDBG_CONF_USER:=$USER} : ${IPKDBG_WGET_OPTS:="--quiet"} : ${IPKDBG_ZSTD:=zstd} ipkdbg_error() { /bin/echo -e "$@" | fold >&2 } ipkdbg_info() { /bin/echo -e "$@" | fold } ipkdbg_help() { /bin/echo -e "\033[1mNAME\033[0m" /bin/echo -e "\tipkdbg - debug OpenBMC applications from an (internally) released firmware" /bin/echo -e /bin/echo -e "\033[1mSYNOPSIS\033[0m" /bin/echo -e "\tipkdbg [-q] RELEASE FILE CORE [PACKAGE...]" /bin/echo -e /bin/echo -e "\033[1mDESCRIPTION\033[0m" /bin/echo -e "\tRELEASE is the firmware release whose packages to install" /bin/echo -e "\tFILE is the absolute path to the binary of interest in the target environment" /bin/echo -e "\tCORE is an optional core file generated by FILE. Pass '-' for no core file" /bin/echo -e "\tPACKAGES will be used to populate a temporary rootfs for debugging FILE" /bin/echo -e /bin/echo -e "\033[1mOPTIONS\033[0m" /bin/echo -e "\t\033[1m-h\033[0m" /bin/echo -e "\tPrint this help." /bin/echo -e /bin/echo -e "\t\033[1m-q\033[0m" /bin/echo -e "\tQuit gdb once done. Intended for use in a scripting environment in combination" /bin/echo -e "\twith a core file, as the backtrace will be printed as an implicit first command." /bin/echo -e /bin/echo -e "\033[1mENVIRONMENT\033[0m" /bin/echo -e "\tThere are several important environment variables controlling the behaviour of" /bin/echo -e "\tthe script:" /bin/echo -e /bin/echo -e "\t\033[1mIPKDBG_OPKG_CACHE\033[0m" /bin/echo -e "\tA package cache directory for opkg. Defaults to empty, disabling the cache." /bin/echo -e /bin/echo -e "\t\033[1mIPKDBG_CONF_HOST\033[0m" /bin/echo -e "\tHostname for access to opkg.conf over the web interface" /bin/echo -e /bin/echo -e "\tDefaults to '${IPKDBG_CONF_HOST}'" /bin/echo -e /bin/echo -e "\t\033[1mIPKDBG_CONF_MNT\033[0m" /bin/echo -e "\tMount-point for access to opkg.conf" /bin/echo -e /bin/echo -e "\tDefaults to '${IPKDBG_CONF_MNT}'" /bin/echo -e /bin/echo -e "\t\033[1mIPKDBG_CONF_LOC\033[0m" /bin/echo -e "\tGeo-location for access to opkg.conf" /bin/echo -e /bin/echo -e "\tDefaults to '${IPKDBG_CONF_LOC}'" /bin/echo -e /bin/echo -e "\t\033[1mIPKDBG_CONF_ROOT\033[0m" /bin/echo -e "\tPath to the directory containing build artifacts, for access to opkg.conf" /bin/echo -e /bin/echo -e "\tDefaults to '${IPKDBG_CONF_ROOT}'" /bin/echo -e /bin/echo -e "\t\033[1mIPKDBG_CONF_USER\033[0m" /bin/echo -e "\tUsername for access to opkg.conf over the web interface" /bin/echo -e /bin/echo -e "\tDefaults to \$USER ($USER)" /bin/echo -e /bin/echo -e "\t\033[1mIPKDBG_GDB\033[0m" /bin/echo -e "\tThe gdb(1) binary to invoke. Automatically detected if unset." /bin/echo -e /bin/echo -e "\t\033[1mIPKDBG_WGET_OPTS\033[0m" /bin/echo -e "\tUser options to pass to wget(1) when fetching opkg.conf. Defaults to" /bin/echo -e "\t'$IPKDBG_WGET_OPTS'" /bin/echo -e /bin/echo -e "\t\033[1mIPKDBG_ZSTD\033[0m" /bin/echo -e "\tThe zstd(1) binary to extract the compressed core dump. Automatically" /bin/echo -e "\tdetected if unset." /bin/echo -e /bin/echo -e "\033[1mEXAMPLE\033[0m" /bin/echo -e "\tipkdbg 1020.2206.20220208a \\" /bin/echo -e "\t\t/usr/bin/nvmesensor - \\" /bin/echo -e "\t\tdbus-sensors dbus-sensors-dbg" } IPKDBG_OPT_QUIT=0 while getopts hq f do case $f in q) IPKDBG_OPT_QUIT=1;; h|\?) ipkdbg_help ; exit 1;; esac done shift $(expr $OPTIND - 1) trap ipkdbg_help EXIT ipkdbg_core_extract() { if [ "-" = "$1" ] then echo - else local src="$(realpath "$1")" local dst="${src%.zst}" command -v $IPKDBG_ZSTD > /dev/null $IPKDBG_ZSTD --decompress --quiet --quiet --force -o "$dst" "$src" || true echo "$dst" fi } IPKDBG_BUILD=$1; shift IPKDBG_FILE=$1; shift IPKDBG_CORE=$(ipkdbg_core_extract "$1"); shift IPKDBG_PKGS=$@ : ${IPKDBG_GDB:=} if [ -n "$IPKDBG_GDB" ] then ipkdbg_info "Using provided gdb command '$IPKDBG_GDB'" else os_id=$(. /etc/os-release; echo ${ID}-${VERSION_ID}) case $os_id in rhel-8.6 | fedora*) IPKDBG_GDB=gdb if [ -z "$(command -v $IPKDBG_GDB)" ] then ipkdbg_error "Please install the gdb package:" ipkdbg_error ipkdbg_error "\tsudo dnf install gdb" ipkdbg_error exit 1 fi ;; rhel*) IPKDBG_GDB=gdb-multiarch if [ -z "$(command -v $IPKDBG_GDB)" ] then ipkdbg_error "Please install the gdb-multiarch package:" ipkdbg_error ipkdbg_error "\tsudo dnf install gdb-multiarch" ipkdbg_error exit 1 fi ;; ubuntu*) IPKDBG_GDB=gdb-multiarch if [ -z "$(command -v $IPKDBG_GDB)" ] then ipkdbg_error "Please Install the gdb-multiarch package" ipkdbg_error ipkdbg_error "\tsudo apt install gdb-multiarch" ipkdbg_error exit 1 fi ;; *) ipkdbg_error "Unrecognised distribution $release_id. Please set IPKDBG_GDB or " \ "install an appropriate gdb binary to invoke" exit 1 ;; esac ipkdbg_info "Using gdb command ${IPKDBG_GDB} ($(command -v $IPKDBG_GDB))" fi ipkdbg_archive_extract() { local offset=$1 local work=$2 tail -n+$offset $0 | base64 --decode - | tar -xz -C $work } ipkdbg_opkg_path() { local root=$1 local arch=$(uname -m) local release_id=$(. /etc/os-release; echo $ID) local release_version_id=$(. /etc/os-release; echo $VERSION_ID) local p=${root}/bin/${arch}/${release_id}/${release_version_id}/opkg if [ ! -x "$p" ] then ipkdbg_error "Unsupported environment:" ipkdbg_error ipkdbg_error "Architecture:\t$arch" ipkdbg_error "Distribution ID:\t$release_id" ipkdbg_error "Distribution Version:\t$release_version_id" exit 1 fi echo $p } if [ ! -f $0 ] then ipkdbg_error "Please execute the script with a relative or absolute path" exit 1 fi IPKDBG_DATA=$(awk '/^__ARCHIVE_BEGIN__$/ { print NR + 1; exit 0 }' $0) IPKDBG_WORK=$(mktemp -t --directory ipkdbg.XXX) IPKDBG_BINS=${IPKDBG_WORK}/tools IPKDBG_ROOT=${IPKDBG_WORK}/root IPKDBG_CONF=${IPKDBG_WORK}/opkg.conf IPKDBG_DB=${IPKDBG_WORK}/database cleanup() { rm -rf $IPKDBG_WORK } trap cleanup EXIT INT QUIT KILL mkdir $IPKDBG_BINS $IPKDBG_DB ipkdbg_archive_extract $IPKDBG_DATA $IPKDBG_BINS IPKDBG_OPKG_BIN=$(ipkdbg_opkg_path $IPKDBG_BINS) ipkdbg_build_gen_path() { local build=$1 local component="$2" echo /${IPKDBG_CONF_MNT}/${IPKDBG_CONF_LOC}/${IPKDBG_CONF_ROOT}/${build}/"$component" } ipkdbg_build_gen_url() { local build=$1 local component="$2" echo https://${IPKDBG_CONF_HOST}/${IPKDBG_CONF_MNT}/${IPKDBG_CONF_LOC}/${IPKDBG_CONF_ROOT}/${build}/${component} } ipkdbg_build_gen_cache() { local build=$1 local component="$2" echo "${HOME}/.cache/ipkdbg/builds/${build}/${component}" } ipkdbg_opkg_conf_gen_path() { local build=$1 ipkdbg_build_gen_path $build bmc_ipk/opkg.conf } ipkdbg_opkg_fetch_path() { local path=$1 local output=$2 cp "$path" "$output" > /dev/null 2>&1 } ipkdbg_opkg_conf_gen_url() { local build=$1 ipkdbg_build_gen_url $build bmc_ipk/opkg.conf } ipkdbg_opkg_fetch_url() { local url=$1 local output=$2 # We don't want URL to wrap ipkdbg_info "Authenticating as user $IPKDBG_CONF_USER" if ! wget --http-user=$IPKDBG_CONF_USER \ --ask-password \ --output-document $output \ $IPKDBG_WGET_OPTS \ $url then ipkdbg_error "Failed to fetch resource" exit 1 fi } ipkdbg_opkg_conf_gen_cache() { local build=$1 ipkdbg_build_gen_cache $build opkg.conf } ipkdbg_opkg_conf_fetch_cache() { local build=$1 local output=$2 local path="$(ipkdbg_opkg_conf_gen_cache $build)" cp "$path" "$output" > /dev/null 2>&1 } ipkdbg_opkg_conf_install() { local build=$1 local output=$2 mkdir -p $(dirname $output) if ! ipkdbg_opkg_conf_fetch_cache $build $output then local cache="$(ipkdbg_opkg_conf_gen_cache $build)" mkdir -p $(dirname $cache) url= ipkdbg_opkg_fetch_path "$(ipkdbg_opkg_conf_gen_path $build)" $cache || (echo "Configuring opkg via $(ipkdbg_opkg_conf_gen_url $build)" && ipkdbg_opkg_fetch_url "$(ipkdbg_opkg_conf_gen_url $build)" $cache) ipkdbg_opkg_conf_fetch_cache $build $output fi } ipkdbg_opkg_db_gen_path() { local build=$1 ipkdbg_build_gen_path $build bmc_ipk/opkg-database.tar.xz } ipkdbg_opkg_db_gen_url() { local build=$1 ipkdbg_build_gen_url ${build} bmc_ipk/opkg-database.tar.xz } ipkdbg_opkg_db_gen_cache() { local build=$1 ipkdbg_build_gen_cache $build opkg-database.tar.xz } ipkdbg_opkg_db_install() { local build=$1 local root=$2 local state=${root}/var/lib/opkg local cache="$(ipkdbg_opkg_db_gen_cache $build)" mkdir -p $state if ! [ -f $cache ] then mkdir -p $(dirname $cache) ipkdbg_opkg_fetch_path "$(ipkdbg_opkg_db_gen_path $build)" $cache || ipkdbg_opkg_fetch_url "$(ipkdbg_opkg_db_gen_url $build)" $cache || rm -f $cache fi tar -xf $cache -C $state 2> /dev/null mkdir -p ${root}/usr/local ln -s ${root}/var ${root}/usr/local/var } ipkdbg_opkg() { $IPKDBG_OPKG_BIN \ $([ -z "$IPKDBG_OPKG_CACHE" ] || echo --cache-dir $IPKDBG_OPKG_CACHE --host-cache-dir) \ -V1 -f $IPKDBG_CONF -o $IPKDBG_ROOT $@ } ipkdbg_gdb_extract_bin() { local core=$1 $IPKDBG_GDB --core $core -ex quit 2> /dev/null | awk -F "[\`']" '/Core was generated by/ { print $2 }' | awk -F " " '{ print $1 }' # Chop off the arguments, we only want the binary path } ipkdbg_opkg_find() { ipkdbg_opkg find $@ | awk '{ print $1 }' } ipkdbg_opkg_find_extra() { local pkg=$1 # Try appending -dbg and -src to the binary package name extra_pkgs="$(ipkdbg_opkg_find ${pkg}-dbg) $(ipkdbg_opkg_find ${pkg}-src)" # If that fails, we probably have a split binary package if [ -z "$extra_pkgs" ] then # Strip the last component off as it's probably the split binary package name and # try again extra_pkgs="$(ipkdbg_opkg_find ${pkg%-*}-dbg) $(ipkdbg_opkg_find ${pkg%-*}-src)" fi echo $extra_pkgs } ipkdbg_opkg_conf_install $IPKDBG_BUILD $IPKDBG_CONF # Extract the binary path from the core if [ '-' = "$IPKDBG_FILE" -a '-' != "$IPKDBG_CORE" ] then IPKDBG_FILE=$(ipkdbg_gdb_extract_bin $IPKDBG_CORE) fi # Update the package database before potentially looking up the debug packages ipkdbg_opkg update # Extract the package name for the binary if [ '-' != "$IPKDBG_CORE" ] then if ipkdbg_opkg_db_install $IPKDBG_BUILD $IPKDBG_DB then # Look up the package for the binary IPKDBG_CORE_PKG="$(IPKDBG_ROOT=$IPKDBG_DB ipkdbg_opkg search ${IPKDBG_DB}${IPKDBG_FILE} | awk '{ print $1 }')" if [ -n "$IPKDBG_CORE_PKG" ] then # Look up the extra (debug, source) packages for the binary package IPKDBG_PKGS="$IPKDBG_PKGS $IPKDBG_CORE_PKG" IPKDBG_PKGS="$IPKDBG_PKGS $(ipkdbg_opkg_find_extra $IPKDBG_CORE_PKG)" fi fi if [ -z "$IPKDBG_PKGS" ] then ipkdbg_error "Unable to determine package-set to install, please specify" \ "appropriate packages on the command line" exit 1 fi fi # Force installation of gcc-runtime-dbg to give us debug symbols for libstdc++ IPKDBG_PKGS="gcc-runtime-dbg $IPKDBG_PKGS" if [ -n "$IPKDBG_OPKG_CACHE" ] then mkdir -p "$IPKDBG_OPKG_CACHE" ipkdbg_opkg install --download-only $IPKDBG_PKGS fi ipkdbg_opkg install $IPKDBG_PKGS | grep -vF 'Warning when extracting archive entry' cat < ${IPKDBG_BINS}/opkg #!/bin/sh exec $IPKDBG_OPKG_BIN -f $IPKDBG_CONF -o $IPKDBG_ROOT \$@ EOF chmod +x ${IPKDBG_BINS}/opkg PATH=${IPKDBG_BINS}:${PATH} $IPKDBG_GDB -q \ -iex "set solib-absolute-prefix $IPKDBG_ROOT" \ -iex "add-auto-load-safe-path $IPKDBG_ROOT" \ -iex "set directories $IPKDBG_ROOT" \ -iex "cd $IPKDBG_ROOT" \ $([ '-' = "$IPKDBG_CORE" ] || echo -ex bt) \ $([ 0 -eq $IPKDBG_OPT_QUIT ] || echo -ex quit) \ ${IPKDBG_ROOT}${IPKDBG_FILE} \ $([ '-' = "$IPKDBG_CORE" ] || echo $IPKDBG_CORE) exit 0 __ARCHIVE_BEGIN__