Andrew Jeffery | c7a446e | 2022-07-21 10:14:54 +0930 | [diff] [blame] | 1 | #!/bin/sh |
| 2 | |
| 3 | set -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 | |
| 14 | ipkdbg_error() { |
| 15 | /bin/echo -e "$@" | fold >&2 |
| 16 | } |
| 17 | |
| 18 | ipkdbg_info() { |
| 19 | /bin/echo -e "$@" | fold |
| 20 | } |
| 21 | |
| 22 | ipkdbg_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 | |
| 92 | IPKDBG_OPT_QUIT=0 |
| 93 | |
| 94 | while getopts hq f |
| 95 | do |
| 96 | case $f in |
| 97 | q) IPKDBG_OPT_QUIT=1;; |
| 98 | h|\?) ipkdbg_help ; exit 1;; |
| 99 | esac |
| 100 | done |
| 101 | shift $(expr $OPTIND - 1) |
| 102 | |
| 103 | trap ipkdbg_help EXIT |
| 104 | |
| 105 | ipkdbg_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 | |
| 120 | IPKDBG_BUILD=$1; shift |
| 121 | IPKDBG_FILE=$1; shift |
| 122 | IPKDBG_CORE=$(ipkdbg_core_extract "$1"); shift |
| 123 | IPKDBG_PKGS=$@ |
| 124 | |
| 125 | : ${IPKDBG_GDB:=} |
| 126 | if [ -n "$IPKDBG_GDB" ] |
| 127 | then |
| 128 | ipkdbg_info "Using provided gdb command '$IPKDBG_GDB'" |
| 129 | else |
| 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))" |
| 172 | fi |
| 173 | |
| 174 | ipkdbg_archive_extract() { |
| 175 | local offset=$1 |
| 176 | local work=$2 |
| 177 | tail -n+$offset $0 | base64 --decode - | tar -xz -C $work |
| 178 | } |
| 179 | |
| 180 | ipkdbg_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 | |
| 198 | if [ ! -f $0 ] |
| 199 | then |
| 200 | ipkdbg_error "Please execute the script with a relative or absolute path" |
| 201 | exit 1 |
| 202 | fi |
| 203 | |
| 204 | IPKDBG_DATA=$(awk '/^__ARCHIVE_BEGIN__$/ { print NR + 1; exit 0 }' $0) |
| 205 | IPKDBG_WORK=$(mktemp -t --directory ipkdbg.XXX) |
| 206 | IPKDBG_BINS=${IPKDBG_WORK}/tools |
| 207 | IPKDBG_ROOT=${IPKDBG_WORK}/root |
| 208 | IPKDBG_CONF=${IPKDBG_WORK}/opkg.conf |
| 209 | IPKDBG_DB=${IPKDBG_WORK}/database |
| 210 | |
| 211 | cleanup() { |
| 212 | rm -rf $IPKDBG_WORK |
| 213 | } |
| 214 | |
| 215 | trap cleanup EXIT INT QUIT KILL |
| 216 | |
| 217 | mkdir $IPKDBG_BINS $IPKDBG_DB |
| 218 | ipkdbg_archive_extract $IPKDBG_DATA $IPKDBG_BINS |
| 219 | |
| 220 | IPKDBG_OPKG_BIN=$(ipkdbg_opkg_path $IPKDBG_BINS) |
| 221 | |
| 222 | ipkdbg_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 | |
| 228 | ipkdbg_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 | |
| 234 | ipkdbg_build_gen_cache() { |
| 235 | local build=$1 |
| 236 | local component="$2" |
| 237 | echo "${HOME}/.cache/ipkdbg/builds/${build}/${component}" |
| 238 | } |
| 239 | |
| 240 | ipkdbg_opkg_conf_gen_path() { |
| 241 | local build=$1 |
| 242 | ipkdbg_build_gen_path $build bmc_ipk/opkg.conf |
| 243 | } |
| 244 | |
| 245 | ipkdbg_opkg_fetch_path() { |
| 246 | local path=$1 |
| 247 | local output=$2 |
| 248 | cp "$path" "$output" > /dev/null 2>&1 |
| 249 | } |
| 250 | |
| 251 | ipkdbg_opkg_conf_gen_url() { |
| 252 | local build=$1 |
| 253 | ipkdbg_build_gen_url $build bmc_ipk/opkg.conf |
| 254 | } |
| 255 | |
| 256 | ipkdbg_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 | |
| 272 | ipkdbg_opkg_conf_gen_cache() { |
| 273 | local build=$1 |
| 274 | ipkdbg_build_gen_cache $build opkg.conf |
| 275 | } |
| 276 | |
| 277 | ipkdbg_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 | |
| 284 | ipkdbg_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 | |
| 300 | ipkdbg_opkg_db_gen_path() { |
| 301 | local build=$1 |
| 302 | ipkdbg_build_gen_path $build bmc_ipk/opkg-database.tar.xz |
| 303 | } |
| 304 | |
| 305 | ipkdbg_opkg_db_gen_url() { |
| 306 | local build=$1 |
| 307 | ipkdbg_build_gen_url ${build} bmc_ipk/opkg-database.tar.xz |
| 308 | } |
| 309 | |
| 310 | ipkdbg_opkg_db_gen_cache() { |
| 311 | local build=$1 |
| 312 | ipkdbg_build_gen_cache $build opkg-database.tar.xz |
| 313 | } |
| 314 | |
| 315 | ipkdbg_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 | |
| 333 | ipkdbg_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 | |
| 340 | ipkdbg_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 | |
| 347 | ipkdbg_opkg_find() { |
| 348 | ipkdbg_opkg find $@ | awk '{ print $1 }' |
| 349 | } |
| 350 | |
| 351 | ipkdbg_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 | |
| 367 | ipkdbg_opkg_conf_install $IPKDBG_BUILD $IPKDBG_CONF |
| 368 | |
| 369 | # Extract the binary path from the core |
| 370 | if [ '-' = "$IPKDBG_FILE" -a '-' != "$IPKDBG_CORE" ] |
| 371 | then |
| 372 | IPKDBG_FILE=$(ipkdbg_gdb_extract_bin $IPKDBG_CORE) |
| 373 | fi |
| 374 | |
| 375 | # Update the package database before potentially looking up the debug packages |
| 376 | ipkdbg_opkg update |
| 377 | |
| 378 | # Extract the package name for the binary |
| 379 | if [ '-' != "$IPKDBG_CORE" ] |
| 380 | then |
| 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 |
| 399 | fi |
| 400 | |
| 401 | # Force installation of gcc-runtime-dbg to give us debug symbols for libstdc++ |
| 402 | IPKDBG_PKGS="gcc-runtime-dbg $IPKDBG_PKGS" |
| 403 | |
| 404 | if [ -n "$IPKDBG_OPKG_CACHE" ] |
| 405 | then |
| 406 | mkdir -p "$IPKDBG_OPKG_CACHE" |
| 407 | ipkdbg_opkg install --download-only $IPKDBG_PKGS |
| 408 | fi |
| 409 | |
| 410 | ipkdbg_opkg install $IPKDBG_PKGS | grep -vF 'Warning when extracting archive entry' |
| 411 | |
| 412 | cat <<EOF > ${IPKDBG_BINS}/opkg |
| 413 | #!/bin/sh |
| 414 | exec $IPKDBG_OPKG_BIN -f $IPKDBG_CONF -o $IPKDBG_ROOT \$@ |
| 415 | EOF |
| 416 | chmod +x ${IPKDBG_BINS}/opkg |
| 417 | |
| 418 | PATH=${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 | |
| 428 | exit 0 |
| 429 | |
| 430 | __ARCHIVE_BEGIN__ |