Προηγμένοι Μικροεπεξεργαστές Εργαστήριο 6 C & Assembly
Real World Situation Στον πραγματικό κόσμο, κανείς δεν γράφει αποκλειστικά assembly Κουραστικό Δύσκολα συντηρήσιμος κώδικας Μηδενική φορητότητα Μεγάλη πιθανότητα για σφάλμα, λόγω έλλειψης checking μηχανισμών Οι καλύτεροι compilers σχεδόν πάντα βγάζουν καλύτερο κώδικα από ότι θα μπορούσε ένας άνθρωπος
Niches for assembly Η χρήση της assembly περιορίζεται σε: Τμήματα κώδικα που έχουν απαιτούν άμεση πρόσβαση στο hardware του υπολογιστή Interrupts Στοίβα Ειδικοί καταχωρητές Ι/Ο Bootstrap κώδικα που πρέπει να εκτελεστεί προκειμένου να στηθεί το κατάλληλο περιβάλλον για υψηλότερου επιπέδου γλώσσες Μικρά και τρομερά κρίσιμα κομμάτια κώδικα, τα οποία δεν μπορεί να βελτιστοποιήσει αρκετά ο compiler
Niches for assembly Ακόμη και σε αυτές τις περιπτώσεις, οι προγραμματιστές προσπαθούν να περιορίσουν την χρήση της assembly στα τελείως απαραίτητα Wrapper συναρτήσεις για προσπέλαση του υλικού Φόρτωμα υποτυπώδους C περιβάλλοντος και πέρασμα σε C
Συνδυασμός C & Assembly Ο πιο συνήθης συνδυασμός της assembly είναι με C κώδικα Η C δεν είναι τόσο υψηλού επιπέδου Οι αφαιρέσεις της C είναι πολύ κοντά στην πραγματικότητα του υπολογιστή Παρέχει πολύ καθαρές διεπαφές
Συνδυασμός C & Assembly Για να συνδυάσουμε C & Assembly πρέπει να ακολουθούμε πιστά τις συμβάσεις κλήσεων συναρτήσεων της πλατφόρμας Ορίζουν την διεπαφή μεταξύ διαφορετικών binary αρχείων, ώστε να μπορούν να συνδυαστούν με όποιον τρόπο και αν έχουν δημιουργηθεί
GCC/x86 Calling Conventions Τα ορίσματα περνιούνται μέσω στοίβας με την αντίθετη σειρά, σαν dwords όποιο και αν είναι το μεγεθός τους Το πρώτο πράγμα που κάνει η συνάρτηση είναι να δημιουργήσει καινούργιο stack frame σώζοντας τον ebp και θέτοντάς τον ίσο με τον esp Οι ebx, esi, edi, ebp, esp, ds, es, ss πρέπει να προστατευθούν από αλλαγές Το αποτέλεσμα επιστρέφεται μέσω του eax ή του edx:eax Η στοίβα επαναφέρεται από τον καλόντα και όχι από την συνάρτηση
GCC/x86 Calling Conventions Πέρα από την κλήση αυτήν καθ'αυτήν Κλήσεις από C προς assembly: Για τις assembly συναρτήσεις και μεταβλητές που χρησιμοποιούνται από C κώδικα πρέπει να δωθούν τα πρωτότυπά τους με το keyword extern και να δηλωθούν ως global στα αρχεία τους. Κλήσεις από assembly προς C: Τα συμβολικά ονόματα του C κώδικα, για να χρησιμοποιηθούν πρέπει να είναι όρατα από άλλα αρχεία (global non-static δλδ) και να δηλωθούν ως extern στον assembly κώδικα
Περίληψη εργαστηρίου Επαναϋλοποιούμε τον editor γράφοντας όσο το δυνατόν λιγότερο assembly Δεν μπορούμε να αποφύγουμε: Interrupts και DOS Services Πέρασμα ανάμεσα σε protected και real mode Χρήση και άμεση προσπέλαση segment registers I/O Task Switching
Περίληψη εργαστηρίου Ιδιομορφία: Ο κώδικάς μας είναι ουσιαστικά bootstrap κώδικας Ετοιμάζει και φορτώνει protected mode Δεν έχουμε λειτουργικό σύστημα Επομένως, δεν μπορούμε να δημιουργήσουμε ένα C πρόγραμμα που να μπορεί να τρέξει μέσα από το περιβάλλον μας Αλλά μπορούμε να καλέσουμε C συναρτήσεις από το assembly προγραμμά μας
Περιγραφή κώδικα Assembly: Λίγο πολύ όπως τα ξέραμε stdlib.asm περιέχει αρχικοποιήσεις protected mode stdio_32bit.asm περιέχει την cls, την printline και την hex2ascii main.asm περιέχει την main μας, βοηθητικές assembly συναρτήσεις για την εφαρμογή μας και την ISR του timer Νέο: c_main.asm περιέχει extern δηλώσεις C συναρτήσεων Νέο: x86_int_21.asm δίνει μία συνάρτηση για κλήση του int 0x21 από protected mode (αντί για το task RM)
Περιγραφή κώδικα H main απλώς καλεί την συνάρτηση c_main, στον C κώδικα, η οποία υλοποιεί τον scheduler Επίσης δίνονται στο αρχείο c_main.c: parser() display() time_delay() Κάνουμε ήδη include τα stdio_32bit.h και stdlib.h που μας δηλώνουν assembly συναρτήσεις και χρήσιμες δομές
Συνδυασμός C & Assembly Πως ενώνονται όλα μαζί; Περνάμε ανεξάρτητα το c_main.c από τον gcc και το main.asm από τον nasm gcc: πρέπει να δημιουργήσουμε object file και όχι executable και να απενεργοποιήσουμε όλες τις βιβλιοθήκες και τις αρχικοποιήσεις nasm: πρέπει να παράξουμε elf object αρχείο (default τύπος του linux και του gcc) και όχι flat όπως μέχρι τώρα Περνάμε τα δύο object files από τον linker του gcc (ld), με οδηγίες τέτοιες ώστε το τελικό executable να καλύπτει τις προδιαγραφές των.com αρχείων Όλα αυτά γίνονται αυτόματα, εκτελώντας το script build.sh $./build.sh
Στόχος άσκησης 1 Για τον scheduler, να γραφτεί το context switch αυτό καθ'αυτό με την λιγότερη δυνατή assembly Πρέπει: Να γράφουμε στο backlink του tss_descr τον selector για το κατάλληλο task Να εξασφαλίζουμε ότι ο descriptor του task δεν είναι busy Να κάνουμε iret
Στόχος άσκησης 2 Να υλοποιηθεί η assembly συνάρτηση store_byte_to_linear_space στο main.asm Μας δίνει την δυνατότητα να προσπελάσουμε θέσεις μνήμης εκτός του τρέχοντος segment, από C Χρησιμοποιείται από το display ώστε να γράψει στην video memory Το πρωτότυπό της δίνεται στην αρχή του c_main.c
Στόχος άσκησης 3 Να υλοποιηθεί η C συνάρτηση load_file, η οποία φορτώνει ένα αρχείο σε έναν buffer χρησιμοποιώντας τα DOS Services για άνοιγμα, διάβασμα και κλείσιμο αρχείο Ο ίδιος αλγόριθμος που είχαμε υλοποιήσει και στα προηγούμενα εργαστήρια Διαφορά: Για να μην υλοποιήσουμε την συνάρτηση σε real mode και επομένως σε assembly, χρησιμοποιούμε την assembly συνάρτηση x86_int_21
void x86_int_21(struct regfile *regs) Παίρνει σαν όρισμα έναν pointer προς δομή τύπου regfile (ήδη ορισμένη στο stdlib.h) Η x86_int_21: περνάει σε real mode φορτώνει τους καταχωρητές με τις τιμές που περιέχονται στην δομή προκαλεί ένα int 0x21 σώζει καταχωρητές και flags πίσω στην δομή επιστρέφει σε protected mode
void x86_int_21(struct regfile *regs) Οπότε για να χρησιμοποιήσουμε ένα service: Δηλώνουμε μία δομή τύπου regfile Αρχικοποιούμε τα μέλη της, στις τιμές που απαιτούνται για να καλέσουμε το DOS service Καλούμε το x86_int_21 με όρισμα pointer προς την δομή που δηλώσαμε Όταν επιστρέψει, τα μέλη της δομής περιέχουν τα αποτελέσματα του service Για παράδειγμα μπορείτε να κοιτάξετε και την υλοποίηση του read_keyboard()