Traceback...

Här diskuterar vi skal, kommandon och klassiska linuxverktyg.
Användarvisningsbild
Osprey
Inlägg: 6310
Blev medlem: 06 apr 2008, 00:09
OS: Ubuntu
Ort: Göteborg/Mölndal/Falkenberg
Kontakt:

Traceback...

Inlägg av Osprey »

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 {
	echo "Learn to use..."
	#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
}
Och här är scriptet "bperror" som också behövs, även det måste finnas i PATH, annars får man ändra i "traceback" och ange full sökväg.

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
Med det här så blir allting så mycket lättare och solen skiner... :D
Senast redigerad av 1 Osprey, redigerad totalt 31 gånger.
Agera genom att ta och behålla initiativet, ta de risker detta kräver...
http://www.enargo.com/it/
OpenVMS Shark - i Linux finns inte SYS$CMKRNL...
Användarvisningsbild
johanre
Serveradmin
Inlägg: 3888
Blev medlem: 22 okt 2006, 09:13
OS: Ubuntu
Utgåva: 22.04 Jammy Jellyfish LTS
Ort: Malmö

Re: Traceback...

Inlägg av johanre »

Snyggt!
Användarvisningsbild
Osprey
Inlägg: 6310
Blev medlem: 06 apr 2008, 00:09
OS: Ubuntu
Ort: Göteborg/Mölndal/Falkenberg
Kontakt:

Re: Traceback...

Inlägg av Osprey »

johanre skrev:Snyggt!
Tack!

Det enda man måste tänka på, är att "bperror" inte är 100% vattentätt, det bygger på Linux standardkod. Men tyvärr är det många program/script som har lite egna felkoder (hit och dit). Så därför ger ibland bperror felaktiga (ehuru korrekta) felutskrifter. Men ihop med "traceback" så kan man nog hitta detta... ;)
Agera genom att ta och behålla initiativet, ta de risker detta kräver...
http://www.enargo.com/it/
OpenVMS Shark - i Linux finns inte SYS$CMKRNL...
Användarvisningsbild
Osprey
Inlägg: 6310
Blev medlem: 06 apr 2008, 00:09
OS: Ubuntu
Ort: Göteborg/Mölndal/Falkenberg
Kontakt:

Re: Traceback...

Inlägg av Osprey »

Ny förbättrad version av scriptet traceback finns nu här ovanför.

Skillnaden är att den här versionen även gör en fullständig traceback på hela funktionsstacken i det script som anropar.

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:
  1. Scriptet "x1" kördes
  2. Scriptet "x1" körde i sin tur scriptet "x2"
  3. Scriptet "x2" körde i sin tur scriptet "x3"
  4. I "x3" har funktionen "x3a" anropats, på rad 38
  5. Funktionen "x3a" anropar i sin tur funktionen "x3b", på rad 5
  6. Funktionen "x3b" anropar i sin tur funktionen "x3c", på rad 9
  7. 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... ;)
Agera genom att ta och behålla initiativet, ta de risker detta kräver...
http://www.enargo.com/it/
OpenVMS Shark - i Linux finns inte SYS$CMKRNL...
Användarvisningsbild
Osprey
Inlägg: 6310
Blev medlem: 06 apr 2008, 00:09
OS: Ubuntu
Ort: Göteborg/Mölndal/Falkenberg
Kontakt:

Re: Traceback...

Inlägg av Osprey »

Det kan förstås givetvis fortfarande finnas buggar i scriptet, jag har bara testat det lite själv på burken här, men det är garanterat helt ofarligt att köra... :)

Traceback gör ingen exit eller något sådant, utan det får man själv ta hand om i överliggande script, beroende på vad som hänt...
Agera genom att ta och behålla initiativet, ta de risker detta kräver...
http://www.enargo.com/it/
OpenVMS Shark - i Linux finns inte SYS$CMKRNL...
Skriv svar

Återgå till "Terminalforum"