Traceback

Här kan ni lägga upp guider av olika slag som Linux Mint eller Linux överlag.

Traceback

Inläggav Osprey » mån jun 19, 2023 3:41 pm

Vet inte om detta är helt rätt plats för något sådant här, men det finns ju inget "terminalforum" här så... ;)

Det händer ibland när man skriver script att man hamnar i ett läge där en felutskrift skrivs ut, men man har ingen aning om hur man hamnade där man är. Det vill säga det blir svårt att spåra vad som egentligen gick fel...

I bash finns en inbyggd funktion som heter "caller", men den kan bara spåra lokalt i den process man just befinner sig (vilket den gör bra). Men ofta är det ju så att man har ett script, som kör ett annat och så vidare och varje script blir ju då en egen process i Linux och då klarar "caller" inte av att följa det hela bakåt.

Efter att ha tröttnat på detta, så kunde jag inte låta bli att hacka ihop ett script som spårar det hela bakåt.

Det enda som är lite sådär med det, är att det kan inte få tag på radnummer mer än en nivå bakåt. För att klara av det så skulle man behöva någon form av direktkommunikation mellan processerna, vilket går men inte är inbyggt i Linux. Visst hade det gått lätt att implementera med signal/catch men den funktionen finns ännu inte i Linux.

Scriptet som gör traceback anropas med "traceback" och vad som står i början av scriptet. Innan det måste man gjort en "source traceback", där alltså scriptet måste befinna sig i "PATH", annars får man ange fullständig sökväg...

Här är scriptet "traceback":
Kod: Markera allt
#! /bin/bash
#
function usage {
cat << EOD

This is a function that may signal an error and show a traceback of the
call-stack which shows how we came there we are.

In opposite to the bash builtin "caller" function, this show across multiple
scripts. However because of that, it can not show internal function calls in
the calling script.

The full syntax of the script is:

      traceback [ERROR-CODE] [MESSAGE] [LINENO]

      ERROR-CODE
          The code derived from a command, if "0" (zero) nothing is done,
          as this i success.

      MESSAGE
          The error-text you want to be written. If unspecified, traceback
          tries to find it out from "bperror", which is a complementary script.

      LINENO
          The line-number you want it to report. If not specified, the function
          can find it self. This option is only meaningful if you want it so
          report something other.

Examples:

      traceback
          Performs a traceback, but writes no message.

      traceback ERROR-CODE
          Tries to find the meaning of the error-code via bperror and performs
          a traceback. The traceback tries to find out line number in scripts.
          The script bperror is needed for this to work.

      traceback ERROR-CODE MESSAGE
          writes your own MESSAGE and performs a traceback. The traceback tries
          to find out line number in scripts.

      traceback ERROR-CODE MESSAGE $LINENO
          Performs a traceback, but uses the specified LINENO and writes the
          specified MESSAGE.

      Note that "LINENO" is something you can specify as you like, but "$LINENO"
      is a bash builtin.

EOD
}
#
function usage {
   usage
   echo
   exit
}
function getProc() {
   SEARCH=$1
   OUT=$2
   PROCESSES=$(ps --no-header -o pid -o ppid -o comm -o args | grep -v grep | grep -v ps)
   IFS=$'\n'
   for PRC in $PROCESSES; do
      XPID=$(echo $PRC | awk '{ print $1 }')
      if [[ $XPID == $SEARCH ]]; then
         COMM=$(echo $PRC | awk '{ print $3 }')
         ARGS=$(echo $PRC | awk '{ $1=""; $2=""; $3=""; print $0 }' | xargs)
         if [[ $COMM != "bash" ]]; then
            echo "$COMM" >> $OUT
            YPID=$(echo $PRC | awk '{ print $2 }')
            getProc $YPID $OUT >> $OUT
            break
         else
            break
         fi
      fi
   done
}
function putOut {
   tac $OUT | sponge $OUT
   while read -r line; do
      SPACE="  $SPACE"
         echo "$SPACE$line" ;
   done < $OUT
}
function showStack() {
   FUNC=$1
   SPACE=$2
   #OUT=$(mktemp)
   let COUNT=0
   while caller $COUNT >> /dev/null; do
      ((COUNT++))
   done
   let I=$COUNT-1
   let IX=$COUNT-1
   let MAXLEN=0
   while true; do
      DATA=$(caller $IX)
      LIN=$(echo $DATA | awk '{ print $1 }')
      FNC=$(echo $DATA | awk '{ print $2 }')
      STRLEN=$(expr length $FNC)
      if [[ $STRLEN -gt $MAXLEN ]]; then
         let MAXLEN=$STRLEN
      fi
      ((I--))
      if [[ $I -eq 0 ]]; then
         break
      fi
   done
   while true; do
      DATA=$(caller $IX)
      LIN=$(echo $DATA | awk '{ print $1 }')
      FNC=$(echo $DATA | awk '{ print $2 }')
      SRC=$(echo $DATA | awk '{ print $3 }')
      if [[ $FNC == "main" ]]; then
         let FNCLEN=$MAXLEN+4
         printf "%s%-${FNCLEN}sline:%5d\n" $SPACE $FUNC $LIN
      else
         let FNCLEN=$MAXLEN+3
         printf "%s-%-${FNCLEN}sline:%5d\n" $SPACE $FNC $LIN
      fi
      ((IX--))
      if [[ $IX -eq 0 ]]; then
         break
      fi
   done
}
function traceback() {
   SPACE=""
   NO_ECHO=false
   CODE=$1
   MESSAGE=$2
   LNUM=$3
   echo
   if [[ $CODE == "-h" || $CODE == "--help" ]]; then
      usage
      exit
   elif [[ $CODE == 0 ]]; then
      echo "<No valid error code>"
      echo "Usage: traceback CODE [MESSAGE] [LINENO]"
   elif [[ ! -z $MESSAGE && $MESSAGE =~ ^[0-9]+$ ]]; then
      echo "<No valid error message>"
      echo "Usage: traceback CODE [MESSAGE] [LINENO]"
   elif [[ -z $MESSAGE ]]; then
      if [[ ! -z $CODE ]]; then
         echo "Possible error (from bperror):"
         echo -n "  "
         bperror $CODE
      else
         # -No need to write any message here, traceback may be called
         # -just to perform a traceback, without writing anything.
         NO_ECHO=true
      fi
   else
      echo "$MESSAGE" >> /dev/tty
   fi
   if [[ $NO_ECHO == false ]]; then
      echo
   fi
   echo "Traceback follows:"
   THIS=$$
   OUT=$(mktemp)
   PROCESSES=$(ps --no-header -o pid -o ppid -o comm -o args | grep -v grep | grep -v ps)
   IFS=$'\n'
   for PROCESS in $PROCESSES; do
      PID=$(echo $PROCESS | awk '{ print $1 }')
      pPID=$(echo $PROCESS | awk '{ print $2 }')
      if [[ $PID == $THIS ]]; then
         getProc $pPID $OUT $LNUM >> $OUT
      fi
   done
   putOut $OUT
   rm $OUT
   SPACE="  $SPACE"
   FUNC=$(caller | awk '{ print $2 }' | sed 's=./==g')
   if [[ -z $(caller 1) ]]; then
      if [[ -z $LNUM ]]; then
         LIN=$(caller | awk '{ print $1 }')
      fi
      FNCLEN=$(expr length $FUNC)
      let FNCLEN+=3
      printf "%s%-${FNCLEN}sline:%4d\n" $SPACE $FUNC $LIN
   else
      showStack $FUNC $SPACE
   fi
   echo
   if [[ $CODE != 0 && -z $MESSAGE ]]; then
      exit
   fi
}

Man får då en utskrift som ser ut som:
Kod: Markera allt
Felutskrift <-- Den man själv angivit, eller den som bperror hittar (eller inget).

Traceback follows:
  x1
    x2
      x3      line:   38
      -x3a    line:    5
      -x3b    line:    9
      -x3c    line:   13

Här ser man då (i det här exemplet) att felet uppstod när:

  • Scriptet "x1" kördes
  • Scriptet "x1" körde i sin tur scriptet "x2"
  • Scriptet "x2" körde i sin tur scriptet "x3"
  • I "x3" har funktionen "x3a" anropats, på rad 38
  • Funktionen "x3a" anropar i sin tur funktionen "x3b", på rad 5
  • Funktionen "x3b" anropar i sin tur funktionen "x3c", på rad 9
  • och... felet som uppstår, uppstår i funktionen "x3c", på rad nummer 13 (eller rättare sagt) - det är där "traceback" anropades...
Om scriptet "x3" inte hade innehållit några funktioner (där felet uppstått), så hade det bara skrivits ut:
Kod: Markera allt
Felutskrift <-- Den man själv angivit, eller den som bperror hittar (eller inget).

Traceback follows:
  x1
    x2
      x3      line:   38
Här kan man då gå tiilbaka för att lokalisera det egentliga probmlemet, som här uppstod i script "x3" på rad 38 (eller åtminstone så var det som sagt där som "traceback" anropades)....

Använd bash och upptäck hela tiden nya möjligheter... ;)

Med det här så blir allting så mycket lättare och solen skiner... :D
Osprey
Ninja
 
Inlägg: 149
Blev medlem: fre maj 30, 2014 3:33 pm
Ort: Falkenberg

Re: Traceback

Inläggav Osprey » mån jun 19, 2023 3:44 pm

Jo scriptet "bperror" som också behövs för att det ska funka:

Kod: Markera allt
# !/bin/bash
#
##########################################################################
#
#   bperror [-b] [-d] [-h] [-l] [-q] [ERRNO]
#
#   A bash version of the perror function in "C" and other languages
#
#   -b|--brief
#      Just write the error message, without error name etc.
#
#   -d|--define
#      Define all errors so they can be used in a script so check
#      against an error. Available errors can be shown with the
#      -l|--list option, or also often shown in the man-page for
#      a command. The error can then be checked as:
#
#         if [[ $? == $ERRNO ]]; then
#            do-something
#         fi
#
#      ERRNO is the name of the possible error. For exemple EBUSY
#      (16) "Device or resource busy".
#
#   -h|--help
#      Show this.
#
#   -l|--list
#      Simply list all existing errors. It is possible to search for
#      the value of a specific error in this list by specifying its
#      name:
#
#         bperror -l | grep error-name
#
#      Of course it is also possible to search for a part of the error-
#      name, this way. Remember that all error-names in Linux is upper-
#      case.
#
#   -q|--quiet
#      Do not write anything if the error has no error message.
#      Otherwise "Unknown error" and the number is written.
#
#   ERRNO   The error code, a numerical value between 1 and 999. Not used
#      when "-d|--define" och "-l|--list" is used.
#
##########################################################################
#
function usage {
   cat << EOD

   bperror [-b] [-d] [-h] [-l] [-q] ERRNO

   A bash version of the perror function in "C" and other languages

   -b|--brief
      Just write the error message, without error name etc.

   -d|--define
      Define all errors so they can be used in a script so check
      against an error. Available errors can be shown with the
      -l|--list option, or also often shown in the man-page for
      a command. The error can then be checked as:

         if [[ \$? == \$ERRNO ]]; then
            do-something
         fi

      ERRNO is the name of the possible error. For exemple EBUSY
      (16) "Device or resource busy".

   -h|--help
      Show this.

   -l|--list
      Simply list all existing errors. It is possible to search for
      the value of a specific error in this list by specifying its
      name:

         bperror -l | grep error-name

      Of course it is also possible to search for a part of the error-
      name, this way. Remember that all error-names in Linux is upper-
      case.

   -q|--quiet
      Do not write anything if the error has no error message.
      Otherwise "Unknown error" and the number is written.

   ERRNO   The error code, a numerical value between 1 and 999.

EOD
}
#   
function stacktrace {
   local i=1 line file func
   while read -r line func file < <(caller $i); do
      echo >&2 "[$i] $file:$line $func(): $(sed -n ${line}p $file)"
      ((i++))
   done
}
print_usage () {
    echo "It is $?"
    stacktrace
    echo "Usage: ${0##*/} ERRNO  (ERRNO=1-999)"
}
#
TEMP=`getopt -obdhlq --long brief,define,help,list,quiet -n $(basename $0) -- "$@"`
if [[ $? -ne 0 ]]; then
   exit
fi
eval set -- "$TEMP"
BP=false
LIST=false
BRIEF=false
QUIET=false
DEFINE=false
CMDNAME=$(basename $0)
while true; do
   case $1 in
      -b|--brief)
         BRIEF=true
         shift
         ;;
      -d|--define)
         DEFINE=true
         shift
         ;;
      -h|--help)
         usage
         exit
         ;;
      -l|--list)
         LIST=true
         shift
         ;;
      -q|--quiet)
         QUIET=true
         shift
         ;;
      --)
         shift
         break
         ;;
      *)
         echo "-Oh, got a star... [$1]"
         exit
         ;;
   esac
done
#
ERRNO=$1
if [[ $ERRNO == 0 ]]; then
   exit
fi
OLDIFS=$IFS
FOUND=false
HEADERS="/usr/include/asm-generic/errno-base.h /usr/include/asm-generic/errno.h"
for HEADER in $HEADERS; do
   if [[ $DEFINE == false && $LIST == false ]]; then
      BP=true
      case $ERRNO in
          [1-9]|[1-9][0-9]|[1-9][0-9][0-9])
         IFS=$'\n'
         ERRS=$(grep "\<$ERRNO\>" $HEADER | grep "^#define")
         for ERR in $ERRS; do
            NUM=$(echo $ERR | awk '{ print $3 }')
            if [[ $NUM == $ERRNO ]]; then
               FOUND=true
               if [[ $BRIEF == false ]]; then
                  ERRMSG=$(echo $ERR | sed 's/#define//g' | xargs)
               else
                  ERRMSG=$(echo $ERR | sed 's/#define//g' | xargs | awk -F \* '{ print $2 }' | xargs)
               fi
               echo "$ERRMSG"
               break
            fi
         done
              ;;
          (*)
              usage
              exit 1
              ;;
      esac
      if [[ $FOUND == true ]]; then
         break
      fi
   elif [[ $DEFINE == true ]]; then
      IFS=$'\n'
      LINES=$(grep "^#define" $HEADER)
      for LINE in $LINES; do
         NAME=$(echo $LINE | awk '{ print $2 }')
         ERR=$(echo $LINE | awk '{ print $3 }')
         if [[ $ERR == [1-9] || $ERR == [1-9][0-9] || $ERR == [1-9][0-9][0-9] ]]; then
            echo -ne "$NAME\r"
            let $NAME=$ERR
         fi
      done
   elif [[ $LIST == true ]]; then
      IFS=$'\n'
      LINES=$(grep "^#define" $HEADER)
      for LINE in $LINES; do
         echo $LINE
      done
   fi
done
IFS=$OLDIFS
if [[ $BP == true ]]; then
   if [[ $FOUND == false && $QUIET == false ]]; then
      echo "Unknown error $ERRNO"
   fi
fi

:D
Osprey
Ninja
 
Inlägg: 149
Blev medlem: fre maj 30, 2014 3:33 pm
Ort: Falkenberg


Återgå till Guider

Vilka är online

Användare som besöker denna kategori: Inga registrerade användare och 3 gäster