Προηγμένοι Μικροεπεξεργαστές Έλεγχος Ροής Προγράμματος
Control Flow εντολές Jump related JMP Jcc (JZ, JNZ, JB, JNB etc) JCXZ, JECXZ LOOP LOOPE, LOOPNE Procedure related CALL RET INT IRET INTO ENTER LEAVE
Επισκόπηση CALL και RET Far και near συναρτήσεις Πέρασμα παραμέτρων μέσω της στοίβας Stack frames Enter και Leave Άλλες εντολές
Συναρτήσεις Ελάχιστες απαιτήσεις: Σε παλιούς επεξεργαστές είσοδος και έξοδος από συναρτήσεις μόνο με jmp Είσοδος: mov [store_pc], ret_address jmp my_function ret_address: Έξοδος: my_function: jmp [store_pc]
Συνάρτησεις Οι σύγχρονοι επεξεργαστές παρέχουν πιο ευέλικτες και έτοιμες λογικές Στους x86 το βασικό ζευγάρι εντολών είναι η CALL και η RET call address = push eip jmp address ret = pop eip
Call και Ret Η call παρέχει διαφορετικές μορφές, ανάλογες με αυτές της jmp call immediate call [mem] call reg Η ret έχει δύο μορφές: ret ret imm (ret και αύξηση του SP κατά imm)
Near και Far συναρτήσεις Σε segmented συστήματα όπως οι x86 οι συναρτήσεις διακρίνονται σε near και far. Near: βρίσκεται στο ίδιο code segment με τον κώδικα που την καλεί Αλλάζει μόνο ο EIP Far: μπορεί να βρίσκεται οπουδήποτε Αλλάζει ο CS και ο EIP Πιο αργή η είσοδος στην συνάρτηση
Πέρασμα παραμέτρων Μέχρι τώρα σχεδόν όλες οι συναρτήσεις μας εκτελούσαν την ίδια λειτουργία πάνω στις ίδιες θέσεις μνήμης Όχι ιδιαίτερα βολικό για μία συνάρτηση Χρειαζόμαστε τρόπο να δώσουμε δυναμικά στην συνάρτηση τα ορίσματα πάνω στα οποία ενεργεί Πέρασμα παραμέτρων μέσω καταχωρητών Πέρασμα παραμέτρων μέσω στοίβας
Πέρασμα παραμέτρων μέσω καταχωρητών Ο πιο εύκολος τρόπος και γρήγορος τρόπος για πέρασμα παραμέτρων Η συνάρτηση περιμένει να βρει τα ορίσματά της σε κάποιους καταχωρητές Συνηθισμένο σε RISC αρχιτεκτονικές (πχ ARM) με πολλούς καταχωρητές Ασύμφορο σε CISC επεξεργαστές Οι x86 έχουν μόνο 6 general purpose καταχωρητές, εκ των οποίων κάποιοι είναι απαραίτητοι για συγκεκριμένες εντολές
Πέρασμα παραμέτρων μέσω στοίβας Πιο αργός τρόπος αλλά πιο ευέλικτος Standard στους x86 Πριν την κλήση βάζουμε τα ορίσματα στην στοίβα Και η συνάρτηση τα βρίσκει σε συγκεκριμένες θέσεις σχετικά ως προς τον πάτο της στοίβας, δηλαδή τον sp
Πέρασμα παραμέτρων μέσω στοίβας Το πρόβλημα είναι ότι ο sp αλλάζει συχνά τιμές (push και pop). Αν αναφερόμαστε σχετικά με τον esp πρέπει να θυμόμαστε διαρκώς το που δείχνει Για το ίδιο όρισμα, διαφορετικά offsets ως προν τον sp Μπορούμε να χρησιμοποιήσουμε τον bp! Δείχνει στο stack segment Δεν έχει συγκεκριμένο ρόλο
Πως γίνεται στην C; Ο κώδικας κάνει push στην στοίβα τα ορίσματα που θέλει να περάσει sp
Πως γίνεται στην C; Ο κώδικας κάνει push στην στοίβα τα ορίσματα που θέλει να περάσει sp arg1 arg2
Πως γίνεται στην C; Ο κώδικας κάνει push στην στοίβα τα ορίσματα που θέλει να περάσει Έπειτα κάνει call (near στο παράδειγμα) sp arg1 arg2
Πως γίνεται στην C; Ο κώδικας κάνει push στην στοίβα τα ορίσματα που θέλει να περάσει Έπειτα κάνει call (near στο παράδειγμα) sp return address arg1 arg2
Πως γίνεται στην C; Ο κώδικας κάνει push στην στοίβα τα ορίσματα που θέλει να περάσει Έπειτα κάνει call (near στο παράδειγμα) Μέσα στην συνάρτηση: sp bp old bp return address arg1 arg2 push bp στην στοίβα mov bp, sp
Πως γίνεται στην C; sp Η συνάρτηση μπορεί να προσπελαύνει δεδομένα σχετικά προς τον bp, όποια τιμή και αν έχει ο sp Πχ mov ax, [bp + 6] ;arg2 bp bp+2 bp+4 bp+6 old bp return address arg1 arg2
Πως γίνεται στην C; sp Η συνάρτηση μπορεί να προσπελαύνει δεδομένα σχετικά προς τον bp, όποια τιμή και αν έχει ο sp bp bp+2 bp+4 bp+6 old bp return address arg1 arg2 Πχ mov ax, [bp + 6] ;arg2 Πριν επιστρέψει η συνάρτηση καθαρίζει την στοίβα
Πως γίνεται στην C; Η συνάρτηση μπορεί να προσπελαύνει δεδομένα σχετικά προς τον bp, όποια τιμή και αν έχει ο sp Πχ mov ax, [bp + 6] ;arg2 sp bp old bp return address arg1 arg2 Πριν επιστρέψει η συνάρτηση καθαρίζει την στοίβα mov sp, bp
Πως γίνεται στην C; Η συνάρτηση μπορεί να προσπελαύνει δεδομένα σχετικά προς τον bp, όποια τιμή και αν έχει ο sp Πχ mov ax, [bp + 6] ;arg2 sp old bp return address arg1 arg2 Πριν επιστρέψει η συνάρτηση καθαρίζει την στοίβα mov sp, bp pop bp bp
Πως γίνεται στην C; Έξω από την συνάρτηση, βγάζουμε από την στοίβα τα ορίσματα: Είτε με pop (αν τα χρειαζόμαστε πάλι) Είτε με add sp, 4 (αν δεν τα χρειαζόμαστε) sp old bp return address arg1 arg2 bp
Παράδειγμα Πρόσθεση 4 ακεραίων int addme(num1, num2, num3, num4)
Παράδειγμα Πρόσθεση 4 ακεραίων int addme(num1, num2, num3, num4) addme: push bp mov bp,sp mov eax,[bp + 4] add eax,[bp + 8] add eax,[bp + 12] add eax,[bp + 16] pop bp ret
Δέσμευση τοπικών μεταβλητών Οι τοπικές μεταβλητές μίας συνάρτησης δεσμεύονται και αποδεσμεύονται δυναμικά Πολλαπλά αντίγραφα της ίδιας μεταβλητής μπορούν να υπάρχουν για συναρτήσεις που καλούν τον εαυτό τους Κάθε στιγμιότυπο μίας συνάρτησης πρέπει να μπορεί να προσπελάσει τις τοπικές μεταβλητές του με τις ίδιες εντολές που χρησιμοποιούν και τα υπόλοιπα στιγμιότυπα Το data segment δεν ταιριάζει σε αυτόν τον ρόλο
Δέσμευση τοπικών μεταβλητών Η C δεσμεύει χώρο στην στοίβα αμέσως μετά την αποθηκευμένη τιμή του bp, μειώνοντας τον sp. Οι τοπικές μεταβλητές προσπελαύνονται χρησιμοποιώντας αρνητικά offset ως προς τον bp Κάθε στιγμιότυπο μίας συνάρτησης έχει διαφορετικό bp, οπότε και προσπελαύνει διαφορετικές τοπικές μεταβλητές
Παράδειγμα int fn(int arg1) { int local1; int local2;...... } local2 local1 old bp return address arg1 stack data old local data old local data oldest dp another ret address bp - 4 bp - 2 bp bp + 4 Old bp
Stack Frame Stack Frame: Abstraction των υψηλότερων επιπέδου γλωσσών προγραμματισμού Περιέχει τα δυναμικά δεδομένα και χαρακτηριστικά μιας συνάρτησης Αποτελείται από τα δεδομένα ανάμεσα: στην θέση που δείχνει ο bp της συνάρτησης στην θέση που δείχνει ο bp της επόμενης
Stack Frame Stack frame 2 Stack frame 1 Stack frame 0 local2 local1 old ebp return address arg1 stack data old local data old local data oldest edp another ret address ebp - 8 ebp - 4 ebp ebp + 8 Old ebp
Stack Frame Οι x86 μας δίνουν εξελιγμένες εντολές ώστε να ελέγξουμε το stack frame Enter arg1, arg2: Σώζει τον παλιό ebp στην στοίβα, φορτώνει στον ebp την τιμή του esp και μειώνει τον esp όσα bytes λέει το πρώτο όρισμα Το δεύτερο όρισμα (nesting level operand) κρατάει το lexical nesting level (0 to 31) της procedure. Το nesting level καθορίζει τον αριθμό των stack frame pointers που υπάρχουν. Leave: Φορτώνει τον esp με την τιμή του ebp και επαναφέρει τον ebp με pop
Stack Frame Function prologue / epilogue: push ebp mov esp, ebp sub esp, [size of local variables]... mov esp, ebp Με ENTER & LEAVE: enter [size of local variables], 0... leave ret pop ebp ret