Δημοσιεύτηκε: 15 Μάιος 2008, 17:24
από Dimitris
Οδηγός μεταγλώττισης πηγαίου κώδικα

1. Εισαγωγή

Oι υπολογιστές αυτό που κάνουν δεν είναι τίποτε άλλο από πρόσθεση, πολλαπλασιασμός και αποθήκευση και μεταφορά δεδομένων. Αυτό επιτυγχάνεται με ηλεκτρονικά κυκλώματα, τα οποία ανάλογα με την είσοδο που δέχονται επιτρέπουν ή όχι τη διέλευση του ρεύματος. Οι καταστάσεις αυτές μπορούν να αναπαρασταθούν στο δυαδικό συστημα ως 0 "δεν παιρνά ρεύμα" ή 1 "παιρνά ρεύμα". Οι οδηγίες ή εντολές που καταλαβαίνει δηλαδή ο υπολογιστής είναι γραμμένες σε γλώσσα μηχανής (machine code). Κώδικας μηχανής ή γλώσσα μηχανής είναι ένα σύστημα οδηγιών και δεδομένων που μπορούν να εκτελεσθούν απ'ευθείας από την κεντρική μοναδα επεξεργασίας του υπολογιστή. Η γλώσσα μηχανής είναι συνήθως δύσκολη στο χειρισμό από τον άνθρωπο, αν και στις πρώτες μέρες των υπολογιστών ήταν αρκετά απλά. Γι'αυτό το λόγο δημιουργήθηκε μια άλλη γλώσσα, η assembly, πιο προσιτή στον άνθρωπο. Η assembly είναι μια χαμηλού επιπέδου γλώσσα για τον προγραμματισμό υπολογιστών. Aν και χρησιμοποιείται ακόμη και σήμερα σε κάποιες εφαρμογές, όπως μικροεπεξεργαστές ή plc, είναι αρκετά δύσχρηστη. Γι'αυτό αναπτύχθηκαν υψηλότερου επιπέδου γλώσσες κατανοητές στον άνθρωπο. Οι γλώσσες προγραμματισμού μπορούν να χωριστούν σε δύο κατηγορίες, compiled και interpreted, ανάλογα με το αν μετατρέπουν τον πηγαίο κώδικα σε κώδικα μηχανής ή αν τον εκτελούν βήμα-βήμα. Μερικά παραδείγματα compiled γλωσσών προγραμματισμού είναι fortran, C, C++, ada, algol, cobol, delphi, pascal. Παραδείγματα interpreted γλωσσών προγραμματισμού, γνωστές και ως scripting γλώσσες, αποτελούν python, java, tcl, ruby. Στον παρών οδηγό θα ασχοληθούμε με τη μεταγλώττιση προγραμμάτων γραμμένα κυρίως σε C, και σε κάποιες περιπτώσεις fortran.

Για τη μεταγλώττιση προγραμμάτων είναι απαραίτητα, εκτός του μεταγλωττιστή, κάποια εργαλεία που διευκολύνουν και αυτοματοποιούν τη διαδικασία. Στο debian (και σε παράγωγά του όπως το ubuntu) η εγκατάσταση των απαραίτητων προγραμμάτων, αν και είναι πολύ πιθανόν να είναι ήδη εγκατεστημένα, γίνεται ως εξής:

Κώδικας: Επιλογή όλων
sudo apt-get install gcc
sudo apt-get install make


Για τους χρήστες FORTRAN το απαραίτητο πακέτο μπορεί να εγκατασταθεί:

Κώδικας: Επιλογή όλων
sudo apt-get install gfortran


Mια άλλη δυνατότητα περιλαμβάνει τη χρήση του μεταγλωττιστή g95, ο οποίος πρέπει να εγκατασταθεί χειροκίνητα. Τα υπόλοιπα εργαλεία, ld, ar, nm, που θα χρειασθούν είναι εγκατεστημένα σε κάθε σύστημα linux. Σε διαφορετική περίπτωση μπορούν να εγκατασταθούν επίσης απλά με τη χρήση της εντολής apt-get, όπως παραπάνω. Άλλες διανομές έχουν διαφορετικό τρόπο εγκατάστασης των πακέτων, όπως το rpm της Red Hat, oι οποίοι δεν πρόκειται να συζητηθούν εδώ.

2. Ο μεταγλωττιστής gcc

Ο μεταγλωττιστής gcc (GNU Compiler Collection, παλιότερα γνωστός ως GNU C Compiler) είναι ένα πρόγραμμα για τη μεταγλώττιση πηγαίου κώδικα σε γλώσσα μηχανής γραμμένου στις γλώσσες C, C++, ή Objective-C (αντικειμενική-C). Ο gcc μεταγλωττίζει ένα ή περισσότερα αρχεία πηγαίου κώδικα, για παράδειγμα πηγαία αρχεία C (file.c), πηγαία αρχεία assemply (file.s) ή προεπεξεργασμένα πηγαία αρχεία C (file.i). Aν η κατάληξη του αρχείου δεν αναγνωρισθεί τότε θεωρείται ως αντικειμενικό αρχείο (object) ή βιβλιοθήκη. Ο gcc καλεί συνήθως έναν προεπεξεργαστή (preprocessor), μεταγλωττίζει τον προεπεξεργασμένο κώδικα σε γλώσσα assembly, τον συναρμολεί και μετά το συνδέει με το linker. H διαδικασία μπορεί να διακοπεί σε ένα από τα προηγούμενα στάδια χρησιμοποιώντας τις επιλογές -c, -S, ή -E. Τα βήματα μπορεί επίσης να διαφέρουν ανάλογα με τη γλώσσα που χρησιμοποιείται. Ως προεπιλογή η έξοδος αποθηκεύεται στο αρχειό a.out. Σε μερικές περιπτώσεις ο gcc δημιουργεί ένα αντικειμενικό αρχειό έχοντας την κατάληξη .o και το αντίστοιχο βασικό όνομα.

Οι επιλογές του προεπεξεργαστή και του linker που δίνονται στη γραμμη εντολών του gcc, μεταβιβάζονται σε αυτά τα εργαλεία όταν εκτελούνται. Στον παρών οδηγό θα σχολιασθούν μόνο οι βασικές επιλογές του gcc, ενώ για μια πλήρη αναφορά μπορείτε να ανατρέξετε στις σελίδες εγχειριδίου (man pages).

Ας ξεκινήσουμε όμως από ένα απλό παράδειγμα. Το αρχείο hallo.c περιλαμβάνει τα εξής:

Κώδικας: Επιλογή όλων
/* hallo.c */
int main(){
   printf("Hallo world\n");
}

H εντολή

Κώδικας: Επιλογή όλων
gcc hallo.c


δέχεται ως αρχείο εισόδου το hallo.c, το μεταγλωτίζει και χωρίς να αποθηκεύει το ενδιάμεσο αντικείμενο, δημιουργεί το εκτελέσιμο με το προεπιλεγμένο όνομα a.out.

H περιφραστική εκδοχή της παραπάνω εντολής θα ήταν:

Κώδικας: Επιλογή όλων
gcc -c hallo.c
gcc -o a.out hallo.c


Η επιλογή -c δηλώνει μεταγλώττιση (compile), ενώ η -ο (οutput) δηλώνει το αρχείο εξόδου. Στην περιφραστική εκδοχή αποθηκεύεται και το ενδιάμεσο αντικείμενο hallo.o.

Αν υποθέσουμε ότι το αρχείο hallo.c περιέχει πολλούς βρόγχους (loops) τον ένα μέσα στον άλλο για τον υπολογισμό πινάκων. Τότε αυτό θα κάνει το πρόγραμμα αρκετά αργό. Σε τέτοιες περιπτώσεις είναι απαραίτητη η χρήση της επιλογής βελτιστοποίησης -Ο όπως στο παρακάτω παράδειγμα:

Κώδικας: Επιλογή όλων
gcc -c -O3 hallo.c


όπου το επίπεδο βελτιστοποίησης τίθεται στο μέγιστο. Υπάρχει περίπτωση, μόνο αν ο κώδικάς σας δεν είναι γραμμένος καθαρα, η έκδοση με βελτιστοποίηση και αυτή χωρίς βελτιστοποίηση να διαφέρουν ως προς τα αποτελέσματα. Αυτό συμβαίνει γιατί η βελτιστοποίηση που εφαρμόζεται τροποποιεί ουσιαστικά τον κώδικα. Το τι σημαίνει "καθαρά" δε θα το σχολιάσουμε εδώ, αλλά μπορείτε να ανατρέξετε σε διάφορα βιβλία προγραμματισμού.

Οι επιλογές προεπεξεργαστή μπορεί να χρησιμοποιηθεί για προγράμματα που πρέπει να λειτουργούν σε διάφορες αρχιτεκτονικές με διαφορετικές τιμές σε μια μεταβλητή. Ας θεωρήσουμε ότι έχουμε σε ένα πρόγραμμα την επιλογή 32-bit ή 64-bit, και ότι σε καθε περίπτωση πρέπει να συμβεί κάτι διαφορετικό. Αυτό δε συμαίνει ότι πρέπει να έχουμε και δύο εκδόσεις του προγράμματος. Μπορεί εύκολα να λυθεί με την επιλογή -D όπως στο παρακάτω παράδειγμα:

Κώδικας: Επιλογή όλων
int main(){
   if (BIT==32){
      printf("%i\n", BIT);
      }
   else if (BIT==64){   
      printf("%i\n",BIT);
      }
   else {
      printf("%i\n",BIT);
      }
}


To οποίο μεταγλωττίζεται με μία από τις επιλογές:

Κώδικας: Επιλογή όλων
gcc -DBIT hallo.c

gcc -DBIT=32 hallo.c

gcc -DBIT=64 hallo.c


Aς υποθέσουμε τώρα το αρχειό mylib.h βρίσκεται στο φάκελο ~/include και ότι θέλουμε να το συμπεριλάβουμε στον κώδικά μας με την εντολή #include "mylib.h". Αυτό θα το κάναμε με την επιλογή -I κατά τη μεταγλώττιση, ή οποία δεχεται τόσο σχετική όσο και απόλυτη διαδρομή.

Κώδικας: Επιλογή όλων
gcc -I~/include hallo.c


Η χρήση μιας βιβλιοθήκης, πχ. libast.a, η οποία βρίσκεται στο φάκελο ~/lib γίνεται με τις παρακάτω επιλογές:

Κώδικας: Επιλογή όλων
gcc -L~/lib -last hallo.c


Πρέπει να σημειωθεί ότι η βιβλιοθήκη libast.a δημιουργείται με το εργαλείο ar, που θα παρουσιαστεί παρακάτω.

Γενικές επιλογές (κατά αλφαβητική σειρά)

-ansi
Eπιβάλει πλήρη συμβατότητα με το πρότυπο ANSI.
-c Δημιουργία ενός συνδέσιμου αρχείου αντικειμένου για κάθε αρχείο πηγαίου κώδικα, αλλά χωρίς κλήση του linker.
-E Προεπεξεργάζεται τα πηγαία αρχεία αλλά δεν τα μεταγλωττίζει. Τυπώνει τα αποτελέσματα στην τυπική έξοδο. Αυτή η επιλογή είναι χρήσιμη για τη μεταβίβαση μερικών επιλογών cpp (C PreProcessor) που διαφορετικά θα σταματούσαν το gcc όπως η -C, -M, ή -P.
-g Συμπεριλαμβάνει πληροφορίες αποσφαλμάτωσης (debugging) για χρήση με το gdb.
-glevel
Παρέχει πληροφορίες για την αποσφαλμάτωση. level πρέπει να είναι 1, 2, ή 3, με το 1 να παρέχει το ελάχιστο πλήθος πληροφοριών. Η προεπιλογή είναι το 2.
--help
Τυπώνει τις συνηθέστερες βασικές επιλόγες και τερματίζει.
-o file
Ορίζει το αρχείο εξόδου ως file. Προεπιλεγμένο είναι το a.out.
-O[level]
Βελτιστοποίηση. level πρέπει να είναι 1, 2, 3, ή 0 (προεπιλεγμένο είναι το 1). Το 0 απενεργοποιεί τη βελτιστοποίηση.
-p Παρέχει πληροφορίες προφιλ για χρήση με prof.
-pedantic
Προειδοποιεί αμετροεπώς.
-pg Παρέχει πληροφορίες προφιλ για χρήση με gprof.
-std=standard
Kαθορίζει το πρότυπο της C του αρχείου εισόδου. Αποδεκτές τιμές είναι:
iso9899:1990, c89 1990 ISO C πρότυπο.
iso9899:199409 1994 προσθήκη στο 1990 ISO C πρότυπο.
iso9899:1999, c99 1999 ISO C αναθεωρημένο πρότυπο.
iso9899:1999, c9x
gnu89 1990 C πρότυπο με GNU επεκτάσεις (η προεπιλεγμένη τιμή).
gnu99, gnu9x 1999 αναθεωρημένο ISO C πρότυπο με GNU επεκτάσεις.

-s Μεταγλωττίζει τα πηγαία αρχεία σε κώδικα assembly, αλλά δεν τα συναρμολογεί.
-v Tυπώνει πληροφορίες έκδοσης.
-V version
Προσπαθεί να εκτελέσει την gcc έκδοση version.
-w Aποσιωπεί τις προειδοποιήσεις.
-W Προειδοποιεί πιο αμετροεπώς από το κανονικό.
-Wall
Ενεργοποιεί όλες τις πιθανές προειδοποιήσεις.
-x language
Περιμένει το αρχείο εισόδου να είναι γραμμένο στη γλώσσα language, που μπορεί να είναι c, objective-c, c-header, c++, ada, f77, ratfor, assembler, java, cpp-output, c++-cpp-ouput, objc-cpp-output, f77-cpp-output, assembler-with-cpp, ή ada. Αν δεν ορισθεί τίποτε τότε μαντεύει τη γλώσσα από την κατάληξη του αρχείου.

Επιλογές προεπεξεργαστή

Ο gcc μεταβιβάζει τις παρακάτω επιλογές στον προεπεξεργαστή:

-Dname[=def]
Οριζει το name με την τιμή def σαν να είχε οριστεί ως #define. Αν δε δοθεί =def, τότε το name ορίζεται με την τιμή 1. -D έχει χαμηλότερη προτεραιότητα από -U.
-Idir
Περιλαμβάνει το dir στη λίστα με τους φακέλους για να ψαξει για αρχεία που πρέπει να συμπεριληφθούν.
-Uname
Aπομακρύνει τον ορισμό το συμβόλου name.

Eπιλογές linker

Ο gcc μεταβιβάζει τις παρακάτω επιλογές στο linker:

-llib
Συνδέει στη βιβλιοθήκη lib.
-Ldir
Ψάχνει στο dir εκτός από προεπιλεγμένους φακέλους για βιβλιοθήκες.
-u symbol
Αναγκάζει το linker να ψάξει στις βιβλιοθηκες για τον ορισμό του symbol, και να συνδέσει στις βιβλιοθήκες που βρήκε.

3. Εργαλεία

3.1 Η εντολή: ld

Το πρόγραμμα ld συνδέει (link) διαφορα αντικειμενα, στη δοσμένη σειρά, σε ένα εκτελέσιμο αντικείμενο (προεπιλογή a.out). Συνήθως εκτελείται αυτόματα από τις εντολές μεταγλώττισης όπως η gcc. Η βασική σύνταξη της εντολής είναι:

ld [options] objfiles

Eπιλογές

-Ldir
Ψάχνει στο dir εκτός από προεπιλεγμένους φακέλους για βιβλιοθήκες.
-o file
Ορίζει το αρχείο εξόδου ως file. Προεπιλεγμένο είναι το a.out.

Για παράδειγμα:

Κώδικας: Επιλογή όλων
ld -o myexe file1.o file2.o file3.o


δημιουργεί το εκτελέσιμο myexe από τα αντικείμενα file1.o file2.o file3.o.

3.2 H εντολή: ar

Χρησιμοποιείται συνήθως για τη δημιούργια βιβλιοθηκών. Η βασική σύνταξη της εντολής είναι:

ar key [args] [posname] [count] archive [files]

Key

d Διαγραφή των files από το archive.
m Μετακίνηση των files στο τέλος του archive.
p Τυπώνει τα files του archive.
q Προσθέτει τα files στο τέλος του archive.
r Αντικαθιστά τα files στο archive.
t Δείχνει τα περιεχομενα του archive ή τα αναφερθέντα files.
x Εξάγει τα περιεχόμενα από το archive ή μόνο τα αναφερθέντα files.

Συνήθη ορίσματα

a Χρηση με r ή m για τοποθέτηση των files στο archive μετά το posname.
b Όπως το a αλλά πρίν το posname.
c Δημιουργία archive "σιωπηλά"
i Όπως το b.
u Χρήση με το r για την αντικατάσταση μόνο των files που άλλαξαν από την τελευταία φορά που προστέθηκαν στο archive.

Για παράδειγμα η εντολή

ar cr libast.a *.o

θα δημιουργήσει μια βιβλιοθηκή με το όνομα libast από όλα τα αρχεία .ο που βρίσκονται στο φάκελο. Αν κάποιο όνομα αρχείο υπάρχει ήδη στη βιβλιοθηκη τοτε θα αντικατασταθεί.

3.3 H εντολή: nm

Με αυτή την εντολή μπορούν να τυπωθούν τα ονόματα των συναρτήσεων που χρησιμοποιούνται στο αντικείμενο. Τα ονόματα τυπώνονται όχι όπως φαίνονται στον πηγαίο κώδικα αλλά με underscores και άλλες λεπτομέρεις που εξαρτώνται από το μεταγλωττιστή. Η εντολή nm είναι πολύ χρήσιμη κατά την αποσφαλμάτωση προγραμμάτων που είναι γραμμένα σε ανάμικτες γλώσσες προγραμματισμού. Η βασική σύνταξη της εντολής είναι:

nm [options] objfiles

Για παράδειγμα το αρχειό hallo.o του πρώτου παραδειγματος στο λειτουργικό mac os x (powerpc) θα δώσει:

00000000 T _main
U _printf
U dyld_stub_binding_helper

4. make

Το make είναι ένα εργαλείο για την αυτόματη δημιουργία προγραμμάτων. Το προεπιλεγμένο αρχείο εισόδου είναι το Makefile, οπότε με την εκτέλεση της εντολής make θα ψάξει το πρόγραμμα για το αρχείο Makefile και αν δε το βρει θα βγάλει μήνυμα λάθους. Ένα απλό Makefile περιλαμβάνει τα εξής:

Κώδικας: Επιλογή όλων
# Αυτό είναι σχόλιο
COM=myexe
OBJ=file1.o file2.o file3.o
LDR=gcc

$(COM): $(OBJ)
   $(LDR) -o $(COM) $(OBJ)
# Τα κενά στην αρχή της παραπάνω γραμμής είναι σημαντικό να είναι tab.

clean:
   rm $(OBJ)
# Ομοίως το κενό πρέπει να είναι tab.


Στα Makefiles υπάρχει η "παραξενιά" να χρησιμοποιείται το tab. Πρέπει απλώς κανείς να το συνηθισει. Αλλά ας πάρουμε το αρχείο από την αρχή. COM=myexe δινει στη μεταβλητή COM την τιμή myexe, η οποία θα είναι το όνομα του εκτελέσιμου. OBJ=file1.o file2.o file3.o λέει στο make ότι τα αντικείμενα που χρειάζονται είναι τα file1.o file2.o file3.o. LDR=gcc ορίζει το πρόγραμμα που θα συνδέσει τα αντικείμενα, θα μπορούσε να ήταν και το ld.

H επόμενη γραμμή λέει ότι το εκτελέσιμο $(COM) εξαρτάται από τα αντικείμενα $(OBJ). Για να δημιουργηθεί δηλαδή το αρχείο myexe πρέπει πρώτα να δημιουργηθούν τα εξαρτόμενα αντικείμενα. Ας υποθέσουμε τώρα ότι τα file1.c και file2.c είναι αρχεία C, ενώ το file3.f είναι αρχείο fortran. Aυτό δε φαίνεται πουθενά στο Makefile, παρόλ' αυτά το make θα καλέσει τους κατάλληλους μεταγλωττιστές, κι αν δεν υπάρχουν θα βγάλει μήνυμα λάθους. Aν πληκτρολογήσουμε make, θα προσπαθήσει να εκτελέσει τις εντολές:

Κώδικας: Επιλογή όλων
gcc -c file1.c
gcc -c file2.c
g77 -c file3.f


Έπειτα θα τα συνδέσει με την εντολή

Κώδικας: Επιλογή όλων
gcc -o myexe file1.o file2.o file3.o


Tελος η εντολή rm $(OBJ) δε θα εκτελεσθεί. Αυτό συμβαίνει γιατί υπάρχουν στο συγκεκριμενο αρχείο 2 στόχοι (targets), ο $(COM) και ο clean. Aν κατα την εκτέλεση της εντολής δε εχουμε δώσει κάποιο στόχο τότε ο προεπιλεγμένος είναι ο ανώτερος, δηλαδή σε αυτή την περίπτωση ο $(COM). Αν πληκτρολογήσουμε

Κώδικας: Επιλογή όλων
make clean


τότε θα πάει στη γραμμή

clean:

όπου δε θα δει καμία εξάρτηση και μετά θα εκτελέσει την εντολή rm $(OBJ).

Όλες οι παραπάνω εντολές και επιλόγες των εντολών που παρουσιασθηκαν παραπάνω, μπορούν με μεγάλη ευκολία να χρησιμοποιηθούν σε ένα Makefile.

5. Επίλογος

Στο μέλλον προβλέπεται αναλυτική περιγραφή του εργαλείου make, τον αποσφαλματωτη gdb καθώς και των άλλων εργαλείων που είναι χρήσιμα για την εγκατάσταση προγραμμάτων κατευθείαν από τον πηγαίο κώδικα. Κάθε πρόταση για προσθήκες και διορθώσεις είναι ευπρόσδεκτη.

6. Βιβλιογραφία

Herold, Helmut (2003), make - Das Profitool zur automatischen Generierung von Programmen, Addison-Wesley.

Robbins, Arnold (2005), UNIX in a nutshell, O' Reilly.

Creative Commons License
Η εργασία υπάγεται στην άδεια Creative Commons Αναφορά-Παρόμοια διανομή 3.0 Ελλάδα