17. Felsökning

17.0 Inledning

Felsökning eller avlusning av program görs numera oftast i interaktiv miljö. De flesta språk innehåller kraftfulla hjälpmedel för sådan verksamhet. Interaktiva avlusningsmetoder anses, förmodligen med rätta, vara överlägsna andra avlusningsmetoder.

Vidare bör nämnas att benämningen DDT nedan inte har med något välkänt och förbjudet bekämpningsmedel att göra, utan står för Dynamic Debugging Technique. Begreppet avlusning (debugging) kommer från en av de första matematikmaskinerna MARK i USA, där vid ett tillfälle en skalbagge fastnat på ett av de elektromekaniska reläerna, varför maskinen inte längre fungerade. I den verkliga loggen står det dock "moth", vilket på svenska (från brittisk engelska) blir mal, mott eller nattfjäril. En bild på denna mal finns på nätet.

På amerikansk engelska heter skalbagge "bug", men översatt från brittisk engelska till svenska blir "bug" i stället vägglus. På brittisk engelska heter skalbagge i stället "beetle".

Tekniken DDT kan användas för "on-line" testning av bl a FORTRAN-program. Sedan användarprogrammet blivit assemblerat eller kompilerat kan det laddas med DDT. Genom att ge kommandon till DDT kan användaren sätta brytpunkter där exekveringen tillfälligtvis avbryts.

Detta ger möjlighet att testa ut program genom att enkelt inspektera godtyckliga programvariabler i brytpunkterna. Variabler och källkod kan även modifieras utan att omkompilering behöver göras.

17.1 Dataflödesanalys

Genom att studera dataflödet kan man kontrollera programmen, dvs man kan finna en del möjliga fel. Titta först på följande avsnitt ur ett Fortran-program
                X = A
                X = B
Här gäller att variabeln X först tilldelas värdet A, för att sedan omedelbart tilldelas värdet B. Man får således aldrig någon nytta av att X först fick värdet på A. Det är därför sannolikt att det rätta programavsnittet i stället skall vara någonting som
                X = A
                Y = B
En annan anomali är
                SUBROUTINE SUB(X,Y,Z)
                Z = Y + W
Här är W odefinierat när det skall användas för att bilda Z. Menade författaren X i stället för W, eller W i stället för X, eller skall W vara i ett COMMON-block?

Som dessa exempel antyder kan vanliga programmeringsfel orsaka anomalier i dataflödet. Sådana fel är stavfel, namnförväxling, felaktig parameteröverföring i proceduranrop, saknade satser och så vidare.

Om man finner en anomali i dataflödet är detta dock inget säkert tecken på ett fel, bland annat kan det vara så att just den betraktade flödesvägen ej kan exekveras. Man kan i viss mån undvika sådana "fel" genom att utnyttja symbolisk exekvering, men sådan exekvering är mycket dyrbar.

Dataflödesanalysen ger förutom upptäckten av anomalier i flödet även värdefull information för dokumentationen av programmet. Den ger information om vilka variabler som får (returnerar) värden vid ett proceduranrop, eller som ger (tilldelar) värden till den anropade proceduren. Den identifierar kopplingen med COMMON och EQUIVALENCE. Den identifierar avsnitt av koden där vissa variabler ej används, liksom i vilken ordning procedurerna anropas.

När en sats i ett program exekveras påverkas förekommande variabler på flera sätt, av vilka vi vill särskilja använda (REFERENCE), tilldela (DEFINE) och glömma (UNDEFINE). När vid exekveringen av en sats värdet på en viss variabel erfordras säger vi att den användes i satsen. Om den i stället ges ett värde säger vi att den tilldelas. Slutligen förekommer det att en variabel tappar sitt värde vid (efter) exekveringen av en viss sats, då säger vi att den glömmes. Som förkortningar för dessa begrepp användes "r", "d" och "u".

När glömmer en variabel sitt värde? I ett blockstrukturerat språk (Algol) så inträffar detta varje gång man går ut från ett inre block, då försvinner alla lokala variabler. På motsvarande sätt gäller i Fortran att uthopp från ett underprogram gör att värdena på alla lokala variabler försvinner, med undantag av de fall då variabeln sparats med SAVE eller är tilldelad med en DATA-sats och ej förändrats.

I Fortran-satsen

                A = B + C
användes B och C, medan A tilldelas, medan i
                I = I + 1
variabeln I både användes och tilldelas. I satsen
                A(I) = B + 1.0
användes I och B, medan A(I) tilldelas.

I Fortran 66 gäller att DO-slingans index glöms (blir odefinierat) när DO-slingan är tillfredsställd. Detta har dock ändrats till det bättre i och med senare versioner (Fortran 77 och Fortran 90). DO-slingans index har nämligen numera sitt nästa värde som slutvärde. Ett annat fall av att en variabel blir odefinierad är när den tilldelas ett odefinierat värde, till exempel i Fortran 66

                X = 1.0
                X = SIGN(X,0.0)
eller i Fortran 77
                X = 1.0
                X = AMOD(2.0,0.0)
Det är illustrativt att betrakta följande avsnitt ur ett Fortran 66 program.
            DO 10 K = 1, N
               X = X + A(K)
               IF (X .LE. 0.0 ) GOTO 20
               Y = Y + A(K)**2
        10     CONTINUE
        20  WRITE (*,*) K
Det intressanta att notera här är att DO-variabeln K ej är odefinierad när satsen X = X + A(K) exekveras efter CONTINUE, men att K är odefinierad då skrivsatsen exekveras efter CONTINUE men definierad då den exekveras efter GOTO-satsen. Till och med Fortran 66 gällde nämligen att DO-indexet vid normal utgång, dvs från en fullständigt genomlöpt DO-slinga, var odefinierat, medan från och med Fortran 77 det i stället har nästa värde. I ovanstående exempel har K således värdet N+1 i fallet fullständigt genomlöpt slinga, dvs det fall som var odefinierat i Fortran 66.

Ett annat problem orsakas av vektorer och matriser. För ett enkelt fall som

                B = A(1) + 1.0
är det inget problem att se att det är det första elementet i A som används, men i
                B = A(K) + 1.0
är det mycket svårare, till och med omöjligt i det fall att K just har lästs in. För att klara av detta problem kan man helt enkelt göra en förenkling på så sätt att man jämställer alla element i en vektor eller matris, ändras ett så ändras alla vad gäller dataflödesanalysen.

Vi återgår nu till de tidigare införda förkortningarna "r", "d", och "u", och använder följder av dessa bokstäver att betrakta hur en viss variabel genomlöper ett program.

För variabeln A i

                      A = A + B
gäller "rd", medan för B gäller enbart "r". I det mer komplicerade fallet
                      A = B + C
                      B = A + D
                      A = A + 1.0
                      B = A + 2.0
                      GO TO 10
gäller för A "drrdr" och för B "rdd". Vi kallar dessa väguttryck (eng. path expressions). Det för A är logiskt, medan det för B är ologiskt, det erfordras naturligtvis en tilldelning före avsnittet, men allvarligare är att B beräknas (olika) två gånger, men det först beräknade används inte.

Om ett väguttryck innehåller någon av delföljderna "ur", "dd" eller "du" bör man studera programmet närmare. Det första innebär att en variabel används direkt efter att den blivit odefinierad, det andra innebär upprepade definitioner utan mellanliggande användning, och det tredje att efter att värdet har definierats så blir det odefinierat utan att någonsin ha använts. - Man kan införa konventionen att från början är alla variabler "u" om de ej har getts initialvärden genom en DATA eller PARAMETER sats.

Vi bör notera att den nämnda förenklingen av matrishanteringen är allvarlig i den meningen att den ofta förhindrar upptäckt av ett felaktigt dataflöde.

            DIMENSION R(100,2)
            READ(5,*) (R(I,1), I = 1, 100)
            CALL SQUARE(R)
            WRITE(6,20) (R(I,2), I = 1, 100)
        20  FORMAT(1X,8F10.2)
            STOP
            END

            SUBROUTINE SQUARE(R)
            DIMENSION R(100,2)
            DO 10 I = 1, 100
               R(I,1) = R(I,2)**2
        10  CONTINUE
            RETURN
            END
Här borde tilldelningssatsen i subrutinens DO-slinga i stället varit
               R(I,2) = R(I,1)**2
eftersom man uppenbarligen blandat ihop de båda kolumnerna, men förenklad dataflödesanalys ger ingen ledning.

17.2 Avlusare

Mycket avancerade felsökningssystem finns under UNIX utnyttjande modern fönsterteknik, ge kommandot man dbx eller man xdbx. Man använder det med kommandot dbx a.out (eller annat programnamn) och när det skrivet (dbx) ger man sedan kommandot run för att starta exekveringen. När man är klar kommer man ur debuggern med kommandot quit. För ytterligare information ge kommandot man dbx.

Själv tycker jag inte om interaktiva avlusningshjälpmedel. En anledning är att jag nu använder Fortran på fem olika datorsystem (DEC ULTRIX, Sun, Silicon Graphics, Cray och IBM PC), samt tidigare även på IBM 7090, IBM 360, CD 6600, DEC-20, DEC VAX/VMS och Hewlett Packard. De flesta av dessa hade helt olika system för felsökning, en del av dem har i och för sig bra system för interaktiv avlusning, men det blir helt enkelt alltför jobbigt att lära sig alla avlusningssystem.

Jag föredrar därför att göra felsökning i satsmiljö, dels utnyttjande systemens möjligheter, dels genom att lägga in egna Fortran satser i koden på de ställen där det verkar gå fel. Ett viktigt exempel på inlagda hjälpmedel är de väljare som kan utnyttjas vid kompileringen.

VAX-11 SYMBOLIC DEBUGGER är ett interaktivt hjälpmedel avsett för avlusning av program skrivna i språk under VMS. Med hjälp av avlusaren (debuggern) kan program exekveras steg för steg och brytpunkter kan sättas, exekveringsföljden kan övervakas, värden på variabler kan undersökas och modifieras. Den är ett mycket kraftfullt hjälpmedel vid uttestning av program.

TOOLPACK är en samling programmeringsverktyg från NAG avsedda för programutveckling i Fortran 77. Ett "verktyg" är ett program som kan användas vid konstruktion, analys, uttestning, förändring eller underhåll av tillämpningsprogram. Inmatning till ett sådant verktyg är användarens program, och programmet producerar en eller båda av

  1. En rapport som analyserar programmet. Ett verktyg, som producerar en rapport utan att programmet exekveras, utför en "statisk analys".

  2. En modifierad version av programmet, i detta fall säges verktyget ha "transformerat" programmet. Exempel på transformering är uppsnyggande av programmets utseende, eller byte av precision mellan enkel och dubbel.
Ett stort antal verktyg finns i TOOLPACK. Några exempel är Utmärkande för TOOLPACK är att alla ingående verktyg kan samverka. - Det beskrivs i "TOOLPACK/1, Introductory Guide". Det är nämnt här för att illustrera vilka verktyg som är tillgängliga för den professionelle Fortranprogrammeraren. Moderna kompilatorer innehåller många motsvarande verktyg.

En del av de modernare kompilatorerna innehåller utmärkta hjälpmedel för att hitta fel. Jag har således upptäckt att NAG:s Fortran 90 kompilator kan upptäcka om två rutiner i ett användarprogram använder olika datatyp i en viss position i argumentlistan, vid anropet av en tredje rutin som befinner sig i ett programbibliotek. Eftersom den tredje rutinen är i ett programbibliotek kan inte systemet normalt avgöra vilken datatyp som är den rätta, utan den rapporterar bara att olika datatyp har utnyttjats vid de båda anropen.

17.3 Felsökningstips

Det finns fyra typer av fel: Jag vill nu i tur och ordning kommentera dessa fyra typer.

Inkompatibilitetsproblem Innehåll Kompilatorer


Senast modifierad: 24 augusti 2001
boein@nsc.liu.se