5. Διάφορα

5.1. Η εντολή nm

Η εντολή nm(1) μπορεί να παραθέσει τη λίστα συμβόλων σε μια δεδομένη βιβλιοθήκη. Λειτουργεί τόσο για τις στατικές όσο και για τις διαμοιραζόμενες βιβλιοθήκες. Για μια δεδομένη βιβλιοθήκη η nm(1) μπορεί να εμφανίσει μια λίστα με τα ονόματα των συμβόλων που έχουν οριστεί, την τιμή κάθε συμβόλου, και τον τύπο του. Μπορεί επίσης να προσδιορίσει το σημείο στον πηγαίο κώδικα της βιβλιοθήκης όπου το σύμβολο ορίστηκε(με βάση όνομα αρχείου και αριθμό γραμμής), εάν οι αντίστοιχες πληροφορίες έχουν ενσωματωθεί στη βιβλιοθήκη (βλ. - προαιρετική παράμετρος -l).

Η πληροφορία της nm που αφορά των τύπο των συμβόλων (symbol type) απαιτεί λίγο περισσότερη επεξήγηση. Ο τύπος παρουσιάζεται ως ένα γράμμα : μικρά γράμματα σημαίνουν ότι το σύμβολο είναι τοπικό (local), ενώ κεφαλαία ότι το σύμβολο είναι καθολικό (εξωτερικό - global,external). Οι χαρακτηριστικοί τύποι συμβόλων περιλαμβάνουν το T (το σύμβολο έχει απλά οριστεί σε αντίστοιχο σημείο στον κώδικα), το D(ανήκει στο τμήμα με τα αρχικοποιημένα δεδομένα), το Β (το σύμβολο ανήκει στο τμήμα με τα μη αρχικοποιημένα σύμβολα), το U(απροσδιόριστο - το σύμβολο χρησιμοποιείται από τη βιβλιοθήκη αλλά δεν ορίζεται σε αυτή), και το W("αδύναμο -weak- σύμβολο": εάν μια άλλη βιβλιοθήκη καθορίζει επίσης αυτό το σύμβολο, εκείνος ο καθορισμός υπερισχύει αυτού).

Εάν ξέρετε το όνομα μιας συνάρτησης, αλλά δεν μπορείτε να θυμηθείτε σε ποια βιβλιοθήκη καθορίστηκε, μπορείτε να χρησιμοποιήσετε την προαιρετική παράμετρο "-ο" της nm (που προτάσσει το όνομα αρχείου σε κάθε γραμμή) μαζί με τη grep για να βρείτε το όνομα της βιβλιοθήκης. Από ένα Bourne shell, μπορείτε να ψάξετε όλες τις βιβλιοθήκες στους καταλόγους /lib, /usr/lib, συμπεριλαμβανομένων των (direct) υποκαταλόγων του /usr/lib, και στον /usr/local/lib για το "cos" ως εξής:
nm -o /lib/* /usr/lib/* /usr/lib/*/* \
      /usr/local/lib/* 2> /dev/null | grep 'cos$' 

Πολύ περισσότερες πληροφορίες για την nm μπορούν να βρεθούν στην αντίστοιχη τεκμηρίωση που εγκαθίσταται τοπικά στο info:binutils#nm.

5.2. Συναρτήσεις κατασκευαστών (constructor) και καταστροφέων (destructor) βιβλιοθήκης

Οι βιβλιοθήκες πρέπει να εξαγάγουν τις ρουτίνες αρχικοποίησης (initialization) και "καθαρισμού" (cleanup) χρησιμοποιώντας τις gcc__attribute__((constructor)) και gcc__attribute__((destructor)). Δείτε τις info pages του gcc για σχετικές πληροφορίες. Οι ρουτίνες των constructors εκτελούνται πριν επιστέψει η dlopen() (ή προτού αρχίσει η main(), εάν η βιβλιοθήκη φορτώνεται κατά το load time). Οι ρουτίνες των destructors εκτελούνται πριν επιστρέψει η dlclose (ή μετά από την exit() ή την ολοκλήρωση της main(), εάν η βιβλιοθήκη φορτώνεται κατά το load time). Τα πρωτότυπα της C (C prototypes) για αυτές τις λειτουργίες είναι:
  void __attribute__ ((constructor)) my_init(void);
  void __attribute__ ((destructor)) my_fini(void);

Οι κοινές βιβλιοθήκες δεν πρέπει να μεταγλωττιστούν με τα ορίσματα του gcc "- nostartfiles" ή "-nostdlib". Εάν χρησιμοποιηθούν αυτά τα ορίσματα, οι ρουτίνες κατασκευαστών/καταστροφέων δεν θα εκτελεσθούν (εκτός αν λειφθούν ειδικά μέτρα).

5.2.1. Ειδικές συναρτήσεις _init και _fini (ΠΑΡΩΧΗΜΕΝΟ/ΕΠΙΚΙΝΔΥΝΟ)

Ιστορικά έχουν υπάρξει δύο ειδικά συναρτήσεις, οι _init και _fini, που μπορούν να χρησιμοποιηθούν για να ελέγξουν τους κατασκευαστές και τους καταστροφείς. Εντούτοις, είναι ξεπερασμένες, και η χρήση τους μπορεί να οδηγήσει σε απρόβλεπτα αποτελέσματα. Οι βιβλιοθήκες σας δεν πρέπει να τις χρησιμοποιήσουν - χρησιμοποιήστε τον κατασκευαστή και τον καταστροφέα ιδιοτήτων συναρτήσεων (function attributes constructor/destructor) που παρουσιάσθηκαν ανωτέρω αντ' αυτού.

Εάν πρέπει οπωσδήποτε να εργαστείτε με παλιά συστήματα ή κώδικα που χρησιμοποιούσε _init ή _fini, ακολουθεί μια περιγραφή για τον τρόπο λειτουργίας τους. Δύο ειδικές λειτουργίες καθορίστηκαν για την αρχικοποίηση και την περάτωση ενός module: οι _init και _fini. Εάν μια λειτουργία ``_init '' εξάγεται (is exported) σε μια βιβλιοθήκη, τότε καλείται όταν πρωτοανοίγεται η βιβλιοθήκη (μέσω της dlopen() ή απλά ως διαμοιραζόμενη βιβλιοθήκη). Σε ένα πρόγραμμα C, αυτό απλά σημαίνει πως ορίσατε (defined) κάποια λειτουργία που ονομάστηκε _init. Υπάρχει μια αντίστοιχη λειτουργία αποκαλούμενη _fini, που καλείται όποτε ένας χρήστης τελειώνει με τη χρήση της βιβλιοθήκης (μέσω μιας κλήσης στην dlclose() που φέρνει τον αριθμό συνδέσεων της στο μηδέν -βλ. dlclose(), ή με την κανονική έξοδο του προγράμματος). Τα πρωτότυπα της C για αυτές τις συναρτήσεις είναι:
  void _init(void);
  void _fini(void);

Σε αυτήν την περίπτωση, κατά τη μεταγλώττιση του αρχείου σε ένα ".o" αρχείο στο gcc, σιγουρευτείτε πως προσθέσατε την προαιρετική παράμετρο του gcc "- nostartfiles". Αυτό εμποδίζει το μεταγλωττιστή της C να συνδέσει (linking) τις βιβλιοθήκες εκκίνησης του συστήματος στο αρχείο. Διαφορετικά, θα πάρετε ένα σφάλμα "multiple-definition" ("πολλαπλός-ορισμός"). Σημειώστε πως τα παραπάνω είναι κάτι εντελώς διαφορετικό από το να μεταγλωττίζετε modules που χρησιμοποιούν τις συνιστώμενες ιδιότητες συναρτήσεων. Οι ευχαριστίες μου στον Jim Mischel και τον Tim Gentry για την πρότασή τους να προστεθεί αυτή η κουβέντα γύρω από τις συναρτήσεις _init και _fini, καθώς επίσης και για τη βοήθεια τους στη δημιουργία της.

5.3. Οι διαμοιραζόμενες βιβλιοθήκες μπορεί να είναι αρχεία εντολών (scripts )

Αξίζει να σημειωθεί ότι ο GNU loader επιτρέπει οι κοινές βιβλιοθήκες να είναι αρχεία εντολών (script files) που χρησιμοποιούν μια εξειδικευμένη scripting γλώσσα, αντί της συνηθισμένης μορφής βιβλιοθηκών. Αυτό είναι χρήσιμο για την έμμεση σύνδεση με άλλες βιβλιοθήκες. Παραδείγματος χάριν, ορίστε η λίστα /usr/lib/libc.so σε ένα από τα συστήματά μου:
/* GNU ld script
   Use the shared library, but some functions are only in
   the static library, so try that secondarily.  */
GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a )

Για περισσότερες πληροφορίες σχετικά, δείτε την τεκμηρίωση texinfo στα scripts του ld linker (ld γλώσσα εντολών). Οι γενικές πληροφορίες είναι στη θέση info:ld#Options και info:ld#Commands, με τις πιθανές εντολές να συζητούνται στο info:ld#Option Commands.

5.4. Εκδόσεις συμβόλων (Symbol versioning) και Αρχείων εντολών έκδοσης (Version scripts)

Τυπικά, αναφορές σε εξωτερικές συναρτήσεις (external functions) είναι δεν είναι "προσδεμένες" (bound) κατά την εκκίνηση μιας εφαρμογής, αλλά γίνονται όταν χρειαστεί. Εάν μια κοινή βιβλιοθήκη δεν είναι πρόσφατα ανανεωμένη (out of date), μπορεί να λείπει ένα απαραίτητο interface- όταν η εφαρμογή προσπαθήσεί να το χρησιμοποιήσει, μπορεί ξαφνικά και απροσδόκητα να αποτύχει.

Μια λύση σε αυτό το πρόβλημα είναι η χρήση symbol versioning (εκδόσεις συμβόλων) μαζί με τα version scripts (αρχεία εντολών έκδοσης). Με το symbol versioning, ο χρήστης μπορεί να δεχθεί μια προειδοποίηση όταν αρχίζει το πρόγραμμα του εάν οι βιβλιοθήκες που χρησιμοποιούνται με την εφαρμογή είναι πάρα πολύ παλιές. Μπορείτε να μάθετε περισσότερα σχετικά από μια συζήτηση στο εγχειριδίου του ld (man) για τα vesrion scripts στο http://www.gnu.org/manual/ld-2.9.1/html_node/ld_25.html.

5.5. GNU libtool

Εάν χτίζετε μια εφαρμογή που θα θέλατε να μπορεί να μεταφερθεί και να λειτουργήσει σε πολλά συστήματα, ίσως θελήσετε να θεωρήσετε το GNU libtool για να χτίσετε και να εγκαταστήσετε τις βιβλιοθήκες. Το GNU libtool είναι ένα γενικό αρχείο εντολών υποστήριξης βιβλιοθηκών (generic library support script). Το libtool κρύβει την πολυπλοκότητα της χρησιμοποίησης των διαμοιραζόμενων βιβλιοθηκών πίσω από μια συνεπή, φορητή διεπαφή. Επιπλέον παρέχει φορητά interfaces για τη δημιουργία object files, στατικές και διαμοιραζόμενες βιβλιοθήκες συνδέσεων (link libraries), σύνδεση εκτελέσιμων (link executables), αποσφαλμάτωση εκτελέσιμων (debug executables), εγκατάσταση βιβλιοθηκών και εγκατάσταση εκτελέσιμων. Περιλαμβάνει επίσης το libltdl, ένα περιτύλιγμα φορητότητας (portability wrapper) για τα δυναμικά προγράμματα φόρτωσης (dynamic loading programs). Για περισσότερες πληροφορίες, δείτε την τεκμηρίωσή του στο http://www.gnu.org/software/libtool/manual.html

5.6. Αφαίρεση συμβόλων για εξοικονόμηση χώρου

Όλα τα σύμβολα που συμπεριλαμβάνονται στα παραγόμενα αρχεία είναι χρήσιμα για το debugging, αλλά πιάνουν χώρο. Εάν χρειάζεστε χώρο, μπορείτε να απομακρύνετε μερικά.

Η καλύτερη προσέγγιση είναι παραχθούν αρχικά τα object files κανονικά, και κάνετε όλο το debugging και τις δοκιμές πρώτα (η αποσφαλμάτωση και οι δοκιμές γίνονται πολύ πιο εύκολα με τη βοήθεια τους). Κατόπιν, μόλις εξετάσετε το πρόγραμμα λεπτομερώς, χρησιμοποιείτε την strip(1) για να αφαιρέσετε τα σύμβολα. Η εντολή strip(1) σας δίνει πλήρη έλεγχο στην απομάκρυνση των συμβόλων που θέλετε - δείτε την τεκμηρίωσή της για λεπτομέρειες.

Μια άλλη προσέγγιση είναι να χρησιμοποιηθούν οι προαιρετικές παράμετροι "-s" και "-s" του GNU ld - η "-S" παραλείπει τις πληροφορίες συμβόλων για τον debugger (αλλά όχι γα όλα τα σύμβολα) από το αρχείο εξόδου, ενώ η "-s" παραλείπει όλες τις πληροφορίες συμβόλων από το αρχείο εξόδου. Μπορείτε να καλέσετε αυτές τις προαιρετικές δυνατότητες μέσω του gcc ως "-Wl,-S" και "-Wl,-s". Εάν πάντα απομακρύνετε τα σύμβολα και αυτές οι επιλογές σας καλύπτουν, αισθανθείτε ελεύθερος να τις χρησιμοποιείτε, αν και παραμένει μια λιγότερο "ελαστική" προσέγγιση.

5.7. Εξαιρετικά μικρά εκτελέσιμα

Ίσως βρείτε το paper Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux πολύ χρήσιμο. Περιγράφει πώς να φτιάξετε ένα πραγματικά μικροσκοπικό εκτελέσιμο προγράμματος . Εδώ που τα λέμε, δεν πρέπει να χρησιμοποιήσετε τα περισσότερα από τα τεχνάσματα που περιγράφει κάτω από πραγματικές συνθήκες, αλλά είναι αρκετά αναλυτικά στην επίδειξη του πραγματικού τρόπου λειτουργίας των ELF.

5.8. C++ εναντίον C

Αξίζει να σημειωθεί πως εάν γράφετε ένα πρόγραμμα σε C++, και καλείτε μια συνάρτηση από μια βιβλιοθήκη σε C, στον C++ κώδικα σας θα πρέπει να καθορίσετε τη λειτουργία C ως "extern C". Διαφορετικά, ο linker δεν θα είναι σε θέση να εντοπίσει τη συνάρτηση C. Εσωτερικά, οι compilers της C++ "κατακρεουργούν" ("mangle") τα ονόματα των συναρτήσεων στην C++ (π.χ., για λόγους τυποποίησης), και πρέπει να τους ειπωθεί ρητά ότι μια δεδομένη λειτουργία πρέπει να κληθεί ως λειτουργία της C (ώστε να μην κάνουν κάτι τέτοιο).

Εάν γράφετε μια βιβλιοθήκη προγράμματος που θα μπορούσε να κληθεί από C ή C ++, συνιστάται να περιλαμβάνετε τις εντολές "extern C" ακριβώς στα αρχεία επικεφαλίδων σας, έτσι ώστε να το κάνετε αυτόματα για τους χρήστες σας. Όταν συνδυάζεται με το συνηθισμένο # ifndef (που μπαίνει στην κορυφή ενός αρχείου για να αποφευχθεί η επαν-εκτέλεση των αρχείων επικεφαλίδων), ένα τυπικό αρχείο επικεφαλίδων, έστω το foobar.h, που μπορεί να χρησιμοποιηθεί είτε από C είτε από C++, θα έμοιαζε με το παρακάτω:
/* Explain here what foobar does */

#ifndef FOOBAR_H
#define FOOBAR_H

#ifdef __cplusplus
extern "C" {
#endif

 ... header code for foobar goes here ...

#ifdef  __cplusplus
}
#endif
#endif

5.9. Κάνοντας ταχύτερη την αρχικοποίηση στη C++

Οι developers του KDE έχουν παρατηρήσει ότι οι μεγάλες εφαρμογές GUI σε εφαρμογές C++ μπορεί να πάρουν κάποιο χρόνο να ξεκινήσουν, εν μέρει λόγω του ότι πρέπει να κάνουν πολλές επαναμεταθέσεις (relocations). Υπάρχουν διάφορες λύσεις σε αυτό το πρόβλημα. Δείτε το Making C++ ready for the desktop (by Waldo Bastian) για περισσότερες πληροφορίες.

5.10. Linux Standard Base (LSB - Βάση Πρότυπων του Linux)

Ο στόχος του LSB project είναι να αναπτυχθεί και να προωθηθεί ένα σύνολο προτύπων (standards) που θα αυξήσει τη συμβατότητα μεταξύ των διαφόρων διανομών Linux και θα επιτρέψει στις εφαρμογές λογισμικού να τρέξουν σε οποιοδήποτε σύστημα συμβατό με το Linux. Η αρχική σελίδα του προγράμματος είναι στο http://www.linuxbase.org.

Ένα συμπαθητικό άρθρο που συνοψίζει πώς να αναπτύξετε LSB-συμβατές εφαρμογές δημοσιεύθηκε τον Οκτώβριο του 2002, το Developing LSB-certified applications: Five steps to binary-compatible Linux applications απ' τον George Kraft IV (Senior software engineer, IBM's Linux Technology Center). Φυσικά, πρέπει να γράψετε κώδικα που έχει πρόσβαση μόνο στο τυποποιημένο στρώμα φορητότητας (standardized compatibility layer) εάν θέλετε ο κώδικά σας να είναι φορητός. Επιπλέον, το LSB παρέχει μερικά εργαλεία έτσι ώστε οι συγγραφείς κώδικα σε C ή C++ να μπορούν να ελέγξουν οι εφαρμογές που γράφουν είναι LSB- compliant - τα αυτά εργαλεία χρησιμοποιούν κάποιες δυνατότητες του linker και ειδικές βιβλιοθήκες για να κάνουν τέτοιους ελέγχους. Προφανώς, θα πρέπει να εγκαταστήσετε τα εργαλεία για να κάνετε αυτούς τους ελέγχους - μπορείτε να τα κατεβάσετε από τη σελίδα του LSB στο διαδίκτυο. Κατόπιν, απλά χρησιμοποιήστε το μεταγλωττιστή "lsbcc" ως τον C/C++ μεταγλωττιστή σας (ο lsbcc εσωτερικά δημιουργεί ένα περιβάλλον σύνδεσης - linking environment- που θα παραπονεθεί εάν ορισμένοι κανόνες LSB δεν ακολουθούνται):
 $ CC=lsbcc make myapplication
  (or)
 $ CC=lsbcc ./configure; make myapplication 
Μπορείτε έπειτα να χρησιμοποιήσετε το πρόγραμμα lsbappchk για να επιβεβαιώσετε ότι το πρόγραμμα χρησιμοποιεί μόνο συναρτήσεις που τυποποιούνται από το LSB:
 $ lsbappchk myapplication
Πρέπει επίσης να ακολουθήσετε τις οδηγίες για δημιουργία πακέτων του LSB (π.χ. χρησιμοποιήστε RPM v3. ονόματα πακέτων κατά LSB, εγκατάσταση add-on software στον κατάλογο /opt by default). Δείτε το άρθρο και το website του LSB για περισσότερες πληροφορίες.

5.11. Συγχωνεύοντας βιβλιοθήκες σε μεγαλύτερες διαμοιραζόμενες (shared) βιβλιοθήκες

Κι αν θελήσετε να δημιουργήσετε πρώτα μικρότερες βιβλιοθήκες και στη συνέχεια να τις συγψωνέυσετε σε μεγαλύτερες; Σε αυτή την περίπτωση, ίσως βρείτε την παράμετρο του ld "--whole-archive" ιδιαίτερα χρήσιμη, αφού μπορεί να χρησιμοποιηθεί για να τη μεταφορά και σύνδεση αρχέιων .a files σε ένα .so αρχείο.

Ακολουθεί ένα παράδειγμα σχετικό με τη χρήση της --whole-archive:
 gcc -shared -Wl,-soname,libmylib.$(VER) -o libmylib.so $(OBJECTS) \
 -Wl,--whole-archive $(LIBS_TO_LINK) -Wl,--no-whole-archive \
 $(REGULAR_LIBS)

Όπως σημειώνεται και στην τεκμηρίωση του ld, σιγουρευτείται πως όταν χρησιμοποιείτε την παράμετρο --no-whole-archive, τη θέτετε στο τέλος, διαφορετικά ο gcc θα προσπαθήσει να συγχωνεύσει ακόμα και τις standard βιβλιοθήκες. Οι ευχριστίες μου στον Kendall Bennett τόσο για την πρόταση όσο και την παροχή της παραπάνω "συνταγής".