blob: ee5bb5ce0c072fd183d0fd922094660a612dcdbd [file] [log] [blame]
#!/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 <<EOF > ${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__