Ο DDD (Data Display Debugger) είναι ένα γραφικό front-end του GDB (Gnu DeBugger). Η σύντομη αυτή εισαγωγή παρουσιάζει τις λειτουργίες των DDD και GDB ώστε να μπορέσετε να τους χρησιμοποιήσετε άμεσα. Στη συνέχεια μπορείτε να μελετήσετε με την ησυχία σας ολόκληρα τα εγχειρίδια, ανάλογα με τις ανάγκες σας. Η εισαγωγή αυτή βασίζεται στο sample session του εγχειριδίου χρήσης του DDD.
Ξεκινούμε με ένα υποδειγματικό πρόγραμμα sample.c
του οποίου ο πηγαίος κώδικας φαίνεται παρακάτω:
/* sample.c -- Sample C program to be debugged with DDD */
#include <stdio.h>
#include <stdlib.h>
static void shell_sort(int a[], int size)
{
int i, j;
int h = 1;
do {
h = h * 3 + 1;
} while (h <= size);
do {
h /= 3;
for (i = h; i < size; i++)
{
int v = a[i];
for (j = i; j >= h && a[j - h] > v; j -= h)
a[j] = a[j - h];
if (i != j)
a[j] = v;
}
} while (h != 1);
}
int main(int argc, char *argv[])
{
int *a;
int i;
a = (int *)malloc((argc - 1) * sizeof(int));
for (i = 0; i < argc - 1; i++)
a[i] = atoi(argv[i + 1]);
shell_sort(a, argc);
for (i = 0; i < argc - 1; i++)
printf("%d ", a[i]);
printf("\n");
free(a);
return 0;
}
Το πρόγραμμα αυτό παρουσιάζει ένα σφάλμα. Κανονικά το
πρόγραμμα sample
πρέπει να ταξινομεί
και να εμφανίζει τα ορίσματά του όπως στο ακόλουθο παράδειγμα:
$ ./sample 8 7 5 4 1 3
1 3 4 5 7 8
$ _
Όμως, με ορισμένα ορίσματα το πρόγραμμα δε δουλεύει σωστά:
$ ./sample 8000 7000 5000 1000 4000
1000 1913 4000 5000 7000
$ _
Ο αριθμός των ορισμάτων είναι σωστός, τα ορίσματα φαίνονται ταξινομημένα αλλά μερικά από αυτά έχουν μεταβληθεί, π.χ. το 1913 αντικατέστησε το 8000.
Θα προσπαθήσουμε να βρούμε το σφάλμα με τη βοήθεια του DDD.
Πρώτα πρέπει να μεταγλωττίσουμε το πρόγραμμα sample.c
για εκσφαλμάτωση, θέτοντας την επιλογή -g
:
$ gcc -g -o sample sample.c
$ _
Τώρα, μπορούμε να καλέσουμε το DDD
με όρισμα το εκτελέσιμο αρχείο
sample
:
$ ddd sampleΕμφανίζεται η διεπαφή του DDD. Το παράθυρο Source Window περιέχει το πηγαίο κώδικα του προγράμματος sample.c; μπορούμε να κινηθούμε με το Scroll Bar.
GNU DDD Version 3.3.9, by Dorothea Lutkehaus and Andreas Zeller.
Copyright © 1995-1999 Technische Universitat Braunschweig, Germany.
Copyright © 1999-2001 Universitat Passau, Germany.
Copyright © 2001-2004 Universitat des Saarlandes, Germany.
Reading symbols from sample...done.
(gdb) _
Reading symbols from sample...done.
(gdb) break sample.c:31
Breakpoint 1 at 0x8048666: file sample.c line 31_
(gdb) _
8000 7000 5000 1000 4000
. Στη συνέχεια
επιλέγουμε το πλήκτρo Run.
Ο GDB μέσω του DDD τώρα εκτελεί το πρόγραμμα. Η εκτέλεση σταματά στο Breakpoint, όπως ανακοινώνεται στο παράθυρο τερματικού του GDB.
(gdb) break sample.c:31
Breakpoint 1 at 0x8048666: file sample.c, line 31.
(gdb) run 8000 7000 5000 1000 4000
Starting program: sample 8000 7000 5000 1000 4000
Breakpoint 1, main (argc=6, argv=0xbffff918) at sample.c:31
(gdb) _
Η τρέχουσα εντολή προς εκτέλεση σημειώνεται με το πράσινο βέλος:
=> a = (int *)malloc((argc - 1) * sizeof(int));
Τώρα
μπορούμε να εξετάσουμε τα περιεχόμενα (τιμές) των μεταβλητών. Για την
εξέταση απλής μεταβλητής (δηλαδή βαθμωτού μεγέθους), απλά κινούμε το
ποντίκι πάνω από το όνομα της μεταβλητής και το αφήνουμε εκεί για λίγο.
Σύντομα εμφανίζεται ένα μικρό pop-up window με τη τιμή της μεταβλητής.
Δοκιμάστε το με το όρισμα argc
για να
δείτε τη τιμή του (6
). Η τοπική
μεταβητή a
δεν έχει
αρχικοποιηθεί ακόμη. Το πιθανότερο είναι οτι θα δείτε μια μη-έγκυρη
τιμή δείκτη σε ακέραιο.
στο Command Tool. Αν
δεν βλέπετε το Command Tool επιλέξτε στο Menu View => Command Tool.
Το πράσινο βέλος προχωρά μια θέση. Οι εντολές του GDB φαίνονται στο
παράθυρο τερματικού. Τώρα μετακινείστε πάλι το δείκτη του ποντικιού
στο a
για να δείτε τη τιμή του που
αρχικοποιήθηκε σε ένα πραγματικό δείκτη.
(gdb) print a[0]
$1 = 0
(gdb) _
(gdb) print a[0]@(argc - 1)
$2 = {0, 0, 0, 0, 0}
(gdb) _
Τώρα προχωρούμε στην ανάθεση τιμών στα στοιχεία του
πίνακα a
:
=> for (i = 0; i < argc - 1; i++)
a[i] = atoi(argv[i + 1]);
Επιλέγουμε το πλήκτρο Next
και βλέπουμε
τη σταδιακή εκχώρηση τιμών. Τα στοιχεία που μεταβάλλονται φωτίζονται.
Για να περάσουμε την εκτέλεση όλου του βρόχου, χρησιμοποιούμε
το πλήκτρο Until
.
Ο GDB
εκτελεί το πρόγραμμα μέχρι να βρεθεί σε αριθμό γραμμής μεγαλύτερο από
τον τρέχοντα, δηλαδή εκτός βρόχου. Έτσι καταλήγουμε στη κλήση της
συνάρτησης
shell_sort
=> shell_sort(a, argc);
Σε αυτό το σημείο, τα περιεχόμενα του a
πρέπει να είναι 8000 7000 5000 1000
4000
. Επιλέγουμε πάλι Next
και προσπερνάμε
το shell_sort
. Ο DDD
σταματά στο
=> for (i = 0; i < argc - 1; i++)
printf("%d ", a[i]);
και βλέπουμε οτι μετά το τέλος του shell_sort
τα περιεχόμενα του a
είναιe 1000,
1913, 4000, 5000, 7000
, δηλαδή το shell_sort
κάπως
χάλασε τα περιεχόμενα του a
.
Για
να βρούμε τι έγινε ξαναεκτελούμε το πρόγραμμα. Αυτή τη φορά δε θα
σταματήσουμε στην αρχικοποίηση, αφού γίνεται σωστά, αλλά θα πάμε κατ'
ευθείαν στη κλήση του shell_sort
.
Διαγράφουμε το παλιό breakpoint επιλέγοντας το και πιέζοντας
το Clear
.
Μετά δημιουργούμε νέο breakpoint στη γραμμή 35
ακριβώς πριν τη κλήση του shell_sort
.
Για τη νέα εκτέλεση επίλέγουμε Program => Run Again
.
Για μια ακόμη φορά ο DDD
σταματά στη γραμμή της κλήσης του shell_sort
:
=> shell_sort(a, argc);
Τώρα θα εξετάσουμε πιο προσεκτικά τι συμβαίνει μέσα
στο shell_sort
. Επιλέγουμε Step
για να μπούμε
μέσα στη κλήση του shell_sort
.
Βρισκόμαστε τη πρώτη εκτελέσιμη γραμμή της συνάρτησης
=> int h = 1;
ενώ το παράθυρο τερματικού μας ενημερώνει για τη συνάρτηση που μόλις μπήκαμε:
(gdb) step
shell_sort (a=0x8049878, size=6) at sample.c:9
(gdb) _
Διαπιστώνουμε οτι η συνάρτηση shell_sort έχει
κληθεί με δύο ορίσματα, τη διεύθυνση του πίνακα ακεραίων a=0x8049878)
και τον ακέραιο size=6,
δηλαδή το μέγεθος του πίνακα. Αυτή η πληροφόρηση λέγεται και stack
frame display,
δηλαδή εμφανίζεται η περίληψη της στοίβας της καλούμενης συνάρτησης.
Για να δούμε το σύνολο της στοίβας των συναρτήσεων επιλέγουμε από το Menu το Status => Backtrace
.
Επιλέγοντας μια γραμμή της στοίβας (ή με τα πλήκτρα Up
και Down
) μπορούμε να
δούμε όλη τη στοίβα. Τα περιεχόμενα κάθε stack frame
εμφανίζονται στο παράθυρο τερματικού του GDB.
Ας δούμε τώρα αν τα ορίσματα του shell_sort
είναι σωστά. Επιστρέφουμε στο σωστό stack frame,
εισάγουμε στο argument
field την έκφραση a[0]@size
και
επιλέγουμε Print
:
(gdb) print a[0] @ size
$4 = {8000, 7000, 5000, 1000, 4000, 1913}
(gdb) _
Έκπληξη! Από πού ήρθε το 1913
?
Η απάντηση είναι απλή: το μέγεθος του πίνακα a, όπως
πέρασε στη συνάρτηση shell_sort
με το όρισμα size, είναι
λάθος: ο πίνακας φαίνεται πιό
μεγάλος κατά μια θέση. Η τιμή
1913
είναι μια τυχαία τιμή που βρίσκεται στη μνήμη μετά το τέλος του πίνακα a
.
Και αυτή η τιμή ταξινομέιται κανονικά
Σημειώστε οτι αντί για Print θα μπορούσαμε να είχαμε επιλέξει Display. Επίσης θα μπορούσαμε να είχαμε εισάγει τις αντίστοιχες εντολές κατ' ευθείαν στο παράθυρο τερματικού του GDB.
Για να βεβαιωθούμε οτι βρήκαμε το σφάλμα, μπορούμε να θέσουμε
στο size
τη σωστή τιμή του. Στο
πηγαίο κώδικα επιλέγουμς το size
και
στη συνέχεια, από τη γραμμή εργαλείων επιλέγουμε Set. Εμφανίζεται
ένα παράθυρο διαλόγου, όπου εισάγουμε τη τιμή και επιλέγουμε OK
ή Apply. Στο παράθυρο τερματικού εμφανίζεται και η σύνταξη της
αντίστοιχης εντολής GDB.
Στη συνέχεια επιλέγουμε Finish
και η
εκτέλεση συνεχίζεται μέχρι την έξοδο από τη συνάρτηση shell_sort
:
(gdb) set variable size = 5Πετύχαμε! Στο
(gdb) finish
Run till exit from #0 shell_sort (a=0x8049878, size=5) at sample.c:9
0x80486ed in main (argc=6, argv=0xbffff918) at sample.c:35
(gdb) _
Data Window εμφανίζονται
τώρα οι σωστές τιμές
1000, 4000, 5000, 7000, 8000
.Μπορούμε να επιβεβαιώσουμε οτι αυτές είναι οι τιμές που τελικά
θα εμφανίσει το πρόγραμμά μας στη πρότυπη έξοδο (stdout) αν συνεχίσουμε
την εκτέλεση του προγράμματος. Επιλέγουμε τπ πλήκτρο Cont
για τη
συνέχιση της εκτέλεσης. Στο παράθυρο τερματικού του GDB βλέπουμε
(gdb) cont
1000 4000 5000 7000 8000
Program exited normally.
(gdb) _
Το μήνυμα Program exited normally.
είναι από το GDB; σημαίνει οτι
τελείωσε η εκτέλεση του προγράμματος sample
.
Τώρα που βρήκαμε το σφάλμα, μπορούμε να διορθώσουμε το πηγαίο
κώδικα. Διορθώνουμε το αρχείο sample.c
, ώστε
η γραμμή
shell_sort(a, argc);
να μεταβληθεί σε
shell_sort(a, argc - 1);
Η διόρθωση μπορεί να γίνει είτε μέσω του συνηθισμένου μας
editor (σε ξεχωριστό παράθυρο τερματικού) ή από το Menu, Source => Edit
Source. Στη συνέχεια μεταγλωττίζουμε πάλι και παράγουμε το
νέο εκτελέσιμο sample
$ gcc -g -o sample sample.c
$ _
και πιστοποιουμε την ορθότητα μέσω του Menu, Program => Run Again
.
(gdb) run
`sample' has changed; re-reading symbols.
Reading in symbols...done.
Starting program: sample 8000 7000 5000 1000 4000
1000 4000 5000 7000 8000
Program exited normally.
(gdb) _
Μια ενδιαφέρουσα δυνατότητα του DDD είναι η ταυτόχρονη παρακολούθηση της εκτέλεσης του προγράμματος, τόσο σε επίπέδο πηγαίου κώδικα αλλά και σε assembly (μνημονική γλώσσα μηχανής). Αυτό μπορεί να γίνει με την επιλογή Menu, View => Machine Code Window. Τα βήματα του προγράμματος ακολουθύνται και στα δύο παράθυρα, οπότε μπορούμε να μελετήσουμε την αντιστοιχία εντολών γλώσσας υψηλού επιπέδου με αυτές γλώσσας μηχανής.
O DDD έχει αρκετές ακόμη επιλογές, όπως για παράδειγμα παρακολούθηση των καταχωρητών (Menu, Status => Registers), δημιουργία και παρακολούθηση Σημάτων (Menu, Status => Signals), lπαρακολούθηση τιμών μεταβλητών (Watch), έλεχγος περιεχομένων τμημάτων μνήμης (Menu, Data => Memory) και πολλά άλλα.
Κλείνουμε το DDD από το Menu, με Program => Exit
ή Ctrl+Q ή
απλά κλείνοντας το παράθυρο.