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