Προηγμένοι Μικροεπεξεργαστές Φροντιστήριο 3 Έλεγχος Ροής Προγράμματος
Επισκόπηση Εντολές Ελέγχου Ροής Υλοποίηση δομών ανώτερου επιπέδου με control flow εντολές Goto και If... then.. else While, do...while και for Optimized χρήση control flow εντολών
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
JMP Jump χωρίς συνθήκη Τύποι: jmp label -> jmp my_loop jmp register -> jmp eax jmp memory -> jmp [sel_function] Το όρισμα δίνει είτε άμεσα, είτε έμμεσα την διεύθυνση που θα φορτωθεί στον IP/EIP
Jcc Jump με συνθήκη Μόνο μία μορφή jcc label Το cc είναι η συνθήκη από την οποία εξαρτάται το jump. Αν η συνθήκη ισχύει φορτώνεται στον EIP το EIP+label (το label δίνει offset προς την τρέχουσα θέση) Αν δεν ισχύει φορτώνεται στον EIP η διεύθυνση της επόμενης εντολής
Jcc Παρέχονται πολλές συνθήκες Αναλυτικό κατάλογο και πληροφορίες μπορείτε να βρείτε στο Brey, σελίδa 196 Οι συνθήκες μπορούν να χωριστούν σε τρεις γενικές κατηγορίες Έλεγχος ενός flag Unsigned comparison Signed comparison
Jcc έλεγχος ενός flag jc: Jump if Carry = 1 Συνώνυμα: JB (jump if below), JNAE (jump if not above or equal) jz: Jump if Zero = 1 Συνώνυμα: JE (jump if equal) js: Jump if Sign = 1 jo: Jump if Overflow=1 jp: Jump if Parity = 1 Συνώνυμα: JPE (jump if parity even)
Jcc Unsigned Comparisons ja: Jump if above (>) Συνώνυμα: JNBE jae: Jump if above or equal (>=) Συνώνυμα: JNC, JNB jb: Jump if below (<) Συνώνυμα: JC, JNAE jbe: Jump if below or equal (<=) Συνώνυμα: JNA je: Jump if equal (==) Συνώνυμα: JZ
Jcc Signed Comparisons jg: Jump if greater (>) Συνώνυμα: JNLE jge: Jump if greater than or equal (>=) Συνώνυμα: JNL jl: Jump if less than (<) Συνώνυμα: JNGE jle: Jump if less than or equal (<=) Συνώνυμα: JNG je: Jump if equal (==) Συνώνυμα: JZ
Άλλες εντολές jcxz, jecxz: jump αν ο cx (ή ο ecx) είναι μηδέν loop: μείωσε τον cx (ή τον ecx) και κάνε jump όσο ο cx (ή ο ecx) είναι διάφορος του μηδενός loope: loop εντολή + έλεγχος για το zero flag
Control flow σε γλώσσες υψηλού Assembly επιπέδου Παρέχει μόνο βασικές δομές για να πετύχουμε έλεγχο ροής Γλώσσες υψηλού επιπέδου Παρέχουν πιο ευέλικτες δομές για τον έλεγχο ροής Πως μπορούμε να υλοποιήσουμε τέτοιες δομές με control flow εντολές της assembly;
GOTO Την παρείχαν οι περισσότερες από τις πρώτες γλώσσες υψηλού επιπέδου Πχ C και Pascal goto loop1 jmp loop1 Απλή και πρωτόγονη εντολή: Μεταφέρει άμεσα τον έλεγχο σε κάποια άλλη γραμμη Ότι δηλαδή κάνει και η jmp
if then else Η πιο συνηθισμένη δομή ελέγχου Εξετάζει μία συνθήκη Αν είναι αληθής εκτελεί το σώμα του then Αν είναι ψευδής εκτελεί το σώμα του else
If (condition) {..then_body.. } else {..else_body.. } if then else
if then else If (condition) {..then_body.. } else {..else_body.. } else:..then_body....else_body.. endif:......
if then else If (condition) {..then_body.. } else {..else_body.. } else:..then_body.. jmp endif..else_body.. endif:......
if then else If (condition) {..then_body.. } else {..else_body.. }..evaluate condition's complement... else: jcc else..then_body.. jmp endif..else_body.. endif:......
if then else If (condition) {..then_body.. } else {..else_body.. } Εξέτασε την αντίστροφη συνθήκη jcc F Εκτέλεσε αν η συνθήκη είναι αληθής T Εκτέλεσε αν η συνθήκη είναι ψευδής
if then else: Παράδειγμα If (num >= 0) { pos_sum += num; } else { neg_sum += num; }
if then else: Παράδειγμα If (num >= 0) { pos_sum += num; } else { neg_sum += num; } mov eax, [num] then: add [pos_sum], eax jmp endif else: add [neg_sum], eax endif:
if then else: Παράδειγμα If (num >= 0) { pos_sum += num; } else { neg_sum += num; } mov eax, [num] cmp eax, 0 jnae else then: add [pos_sum], eax jmp endif else: add [neg_sum], eax endif:
if then If (condition) {..then_body.. } Εξέτασε την αντίστροφη συνθήκη jcc F Εκτέλεσε αν η συνθήκη είναι αληθής T
Υπολογισμός συνθηκών Γενικά If (operand1 rel_operation operand2) Cmp operand1, operand2 jcc else ;cc->opposite of rel_operation == -> JNE!= -> JE < -> JNB ή JNL <= -> JNBE ή JNLE > -> JNA ή JNG >= -> JNAE ή JNGE
Υπολογισμός συνθηκών Αν οι συνθήκες όμως είναι πιο περίπλοκες, τι κάνουμε; Πχ: if (((a!= 0) && ( b!= 0)) (a == b))
Υπολογισμός συνθηκών Πρώτα σπάμε το if σε κομμάτια με βάση την σειρά υπολογισμού των συνθηκών if (((a!= 0) && ( b!= 0)) (a == b)) if ((a!= 0) && ( b!= 0)) {...} if (a == b) {...} if (a!= 0) { if ( b!= 0) {...} } if (a == b) {...}
Υπολογισμός συνθηκών Μετά μετατρέπουμε μία-μία τις απλές συνθήκες if (a!= 0) mov eax, [a] { mov ebx, [b] } if ( b!= 0) {...} if (a == b) {...} cmp eax, 0 jz endif cmp ebx, 0 jz endif then:... endif:
Υπολογισμός συνθηκών Μετά μετατρέπουμε μία-μία τις απλές συνθήκες if (a!= 0) mov eax, [a] mov eax, [a] { mov ebx, [b] mov ebx, [b] } if ( b!= 0) {...} if (a == b) {...} cmp eax, 0 jz endif cmp eax,ebx jnz endif cmp ebx, 0 jz endif then:... endif: then:... endif:
Υπολογισμός συνθηκών Τέλος ενώνουμε όλα τα κομμάτια σε έναν κώδικα if (a!= 0) mov eax, [a] { mov ebx, [b] cmp eax, 0 if ( b!= 0) {...} jz second_if } cmp ebx, 0 if (a == b) {...} jz second_if jmp then second_if: cmp eax,ebx jnz endif then:.. endif:
Υπολογισμός συνθηκών Μετά μετατρέπουμε μία-μία τις απλές συνθήκες if (a!= 0) mov eax, [a] { mov ebx, [b] cmp eax, 0 if ( b!= 0) {...} jz second_if } cmp ebx, 0 if (a == b) {...} jz second_if jmp then second_if: cmp eax,ebx jnz endif then:.. endif:
Υπολογισμός συνθηκών Μετά μετατρέπουμε μία-μία τις απλές συνθήκες if (a!= 0) mov eax, [a] { mov ebx, [b] cmp eax, 0 if ( b!= 0) {...} jz second_if } cmp ebx, 0 if (a == b) {...} jnz then second_if: cmp eax,ebx jnz endif then:.. endif:
While Η βασική εντολή βρόχου While (condition) {...while_body... }
While Μπορεί να αναλυθεί σε πιο βασικές εντολές ελέγχου ροής: loop_start: if (condition) {..loop_body... goto loop_start } loop_end:
while (num >= 0) { sum += num; num--; } While Παράδειγμα
While Παράδειγμα while (num >= 0) { sum += num; num--; } loop_start: if (num >= 0) { sum += num; num--; goto loop_start; } loop_end:
While Παράδειγμα while (num >= 0) { sum += num; num--; } mov eax, [num] loop_start: cmp eax, 0 jnae loop_end add [sum], eax dec eax jmp loop_start loop_end:
Do - while Παρόμοια με την while, αλλά τσεκάρει την συνθήκη στο τέλος του βρόχου do {...loop_body... } while(condition)
Do - while Μπορεί και αυτή να αναλυθεί σε πιο βασικές εντολές ελέγχου ροής: loop_start:..loop_body... if (condition) { } loop_end: goto loop_start
For Το for μπορούμε να το δούμε σαν ειδική μορφή του while for(initialization; condition; effect) {loop_body;}
For Το for μπορούμε να το δούμε σαν ειδική μορφή του while for(initialization; condition; effect) {loop_body;} Initialization; while (condition) {... loop_body... effect }
Control flow optimizations Μάθε τα δεδομένα σου! Αν έχεις μία σύνθετη συνθήκη με AND Και οι τιμές που παίρνει μία μεταβλητή κάνουν μία από τις συνθήκες σχεδόν πάντα να μην ισχύει Βάλε αυτήν την συνθήκη πρώτη Αν έχεις μία σύνθετη συνθήκη με OR Και οι τιμές που πέρνει μία μεταβλητή κάνουν μία από τις συνθήκες σχεδόν πάντα να ισχύει Βάλε αυτήν την συνθήκη πρώτη
Control flow optimizations Οι σύνθετες AND συνθήκες υπολογίζονται έως ότου βρεθεί μία false υποσυνθήκη ή υπολογιστούν όλες οι υποσυνθήκες. Αν ξέρουμε ότι συνήθως μία συγκεκριμένη υποσυνθήκη θα είναι false, μας συμφέρει να σταματήσουμε τον υπολογισμό όσο το δυνατόν πιο νωρίς, βάζοντας την πρώτη
Control flow optimizations Οι σύνθετες OR συνθήκες υπολογίζονται έως ότου βρεθεί μία true υποσυνθήκη ή υπολογιστούν όλες οι υποσυνθήκες. Αν ξέρουμε ότι συνήθως μία συγκεκριμένη υποσυνθήκη θα είναι true μας συμφέρει να σταματήσουμε τον υπολογισμό όσο το δυνατόν πιο νωρίς, βάζοντας την πρώτη
Control flow optimizations Loop unrolling for (i = 0; i < 100; i++) { sum += array[i]; } for (i = 0; i < 100; i +=4) { sum += array[i]; sum += array[i+1]; sum += array[i+2]; sum += array[i+3]; }
Control flow optimizations Loop unrolling mov eax, 0 cmp eax, 100 jae loop_end loop_start: add ebx, [array + eax] inc eax cmp eax, 100 jb loop_start loop_end: mov eax, 0 cmp eax, 100 jae loop_end loop_start: add ebx, [array + eax] add ebx, [array + eax + 1] add ebx, [array + eax + 2] add ebx, [array + eax + 3] add eax, 4 cmp eax, 100 jb loop_start loop_end:
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 Άλλες εντολές
Συναρτήσεις Επαναχρησιμοποιήσιμες δομές κώδικα με συγκεκριμένα σημεία εισόδου και εξόδου Κατά την είσοδο μεταφέρεται ο έλεγχος στην αρχή της συνάρτησης Κατά την έξοδο μεταφέρεται ο έλεγχος πίσω στον κώδικα που κάλεσε την συναρτηση Προφανώς για την είσοδο και την έξοδο απαιτούνται control flow εντολές
Συναρτήσεις Ελάχιστες απαιτήσεις: Σε παλιούς επεξεργαστές είσοδος και έξοδος από συναρτήσεις μόνο με jmp Είσοδος: mov [store_pc], ret_address jmp my_function ret_address: Έξοδος: my_function: jmp [store_pc]
Συνάρτησεις Οι σύγχρονοι επεξεργαστές παρέχουν πιο ευέλικτες και έτοιμες λογικές Στους x86 το βασικό ζευγάρι εντολών είναι η CALL και η RET call address = ret = push eip jmp address 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 Πιο αργή η είσοδος στην συνάρτηση
Near και Far συναρτήσεις Κάθε assembler δίνει τον δικό του τρόπο για να ξεχωρίζουμε near και far συναρτήσεις και calls. Στον Nasm: Η συνάρτηση δεν μαρκάρεται σαν near ή far Το τι είναι καθορίζεται από το call που θα την καλέσει και από την ret που θα την κλείσει
Near και Far συναρτήσεις call: Call με immediate στην μορφή 'seg:offset' είναι far Call με μνήμη είναι far αν περιέχει το keyword FAR call FAR [mem]: EIP [mem], CS [mem+4] Call με καταχωρητή είναι πάντα near ret: retf για return από far συνάρτηση retn ή απλώς ret για return από near
Πέρασμα παραμέτρων Μέχρι τώρα σχεδόν όλες οι συναρτήσεις μας εκτελούσαν την ίδια λειτουργία πάνω στις ίδιες θέσεις μνήμης Όχι ιδιαίτερα βολικό για μία συνάρτηση Χρειαζόμαστε τρόπο να δώσουμε δυναμικά στην συνάρτηση τα ορίσματα πάνω στα οποία ενεργεί Πέρασμα παραμέτρων μέσω καταχωρητών Πέρασμα παραμέτρων μέσω στοίβας
Πέρασμα παραμέτρων μέσω καταχωρητών Ο πιο εύκολος τρόπος και γρήγορος τρόπος για πέρασμα παραμέτρων Η συνάρτηση περιμένει να βρει τα ορίσματά της σε κάποιους καταχωρητές Συνηθισμένο σε 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 λέει το πρώτο όρισμα Leave: Φορτώνει τον esp με την τιμή του ebp και επαναφέρει τον ebp με pop