Jump to content



Pointers,Registers,Stack,Functions


Dr.Paneas

Recommended Posts

Παιδια διαβαζω c++ και καπου εχει ενα κεφαλαιο για το τι γινεται οτι καλείται μια Function. Μπερδευτηκα τελειως ομως:p . Θα σας πω τι καταλαβα να μου πειτε αν καταλαβα κατι λαθος.

Λοιπον οταν πραγματοποιείται ενα Function call, ο κώδικας "πετάγεται" στην καλούμενη function, στις παραμέτρους και γενικότερα στο body της Function και την εκτελεί. Οταν ολοκληρωθεί η Function, επιστρέφει μια τιμή (εκτος κι αν ειναι void) και ο κώδικας επιστρέφει απο κει που ηταν.

Registers είναι μια ειδική περιοχή στην μνήμη built-in στον CPU και φροντίζει τις απαραίτητες δουλείες ρουτίνας. Ενα set of registers ειναι υπεύθυνο καθε στιγμη να δείχνει την επόμενη γραμμή του κώδικα. Αυτοι οι παρέα των registers ονομάζεται Instructions Pointer. Ειναι δουλεία του να παρακολουθεί στενα και να κρατάει στοιχεία για την επόμενη γραμμή του κώδικα που πρόκειται να εκτελεστεί.

πχ


100 γραμμη_κώδικα int x=5;
101 γραμμή_κώδικα int y=7;
102 γραμμη_κώδικα cout <<x;
103 γραμμη_κώδικα Func(x,y);
104 γραμμη_κώδικα y=9;
105 γραμμη_κώδικα return;

Εδω σε αυτο το παράδειγμα το Intruction Pointer ειναι στην γραμμή 102.

Το stack ειναι μια ειδική περιοχή μνήμης η οποία μνήμη έχει δεσμευτεί από το πρόγραμμα για να κρατάει τα δεδομένα που απαιτούνται από τις Functions. Stack - Στοιβα ονομαζεται λογω το LIFO. Πχ μια στοιβα με κερματα. Βαζω τρια Ευρω το τραπεζι το ενα πανω στο αλλο. Και μετα τα ξανα περνω. Αυτο που έβαλα τελευταίο είναι αυτό που το παιρνω πρώτο οταν τα μαζέυω από το τραπέζι.

Οταν κανω Push τα Data απο το Stack, το stack μεγαλώνει!! Οταν κανω Pop τα Data απο το Stack το stack μικραίνει. Μια στοιβα απο πιατα αποτελεί ένα κοινό παράδειγμα.

Aλλα το σωστό παράδειγμα είναι αυτό με τα συρτάρια αντι για πίάτα. Η κορυφή -top- του stack είναι το συρτάρι το οποίο δείχνει ο stack pointer (ο οποίο είναι ένας ακόμα register).

Καθένα από τα συρτάρια έχει μια σειριακή-διαδοχική address και μία από αυτές ανοίκει στον stack pointer register. Ο,τι βρίσκεται κάτω από αυτή την μαγική address,γνωστή ως κορυφή της στοίβας, είναι on the stack (τι εννοει ; οτι βρίσκεται μεσα στο stack λογικα). Ο,τι είναι πάνω από αυτήν είναι off the stack (δηλαδή ;;;;). Δείτε το παράδειγμα:

Link to comment
Share on other sites

Οταν τα Data γινεται put on the stack, τοποθετούνται σε ενα συρτάρι πάνω απο τον stack pointer και μετα ο stack pointer μετακινείται σε νεα data. Οταν παλι τα Data ειναι popped off απο το Stack, αυτο που πραγματικά συμβαίνει είναι οτι η address του stack pointer έχει αλλάξει πηγαίνωντας κάτω.

Δείτε το παράδειγμα.

post-364-1416073175,878_thumb.jpg

Link to comment
Share on other sites

Stack και Functions

1 ==> Η Address που βρίσκεται μεσα στον Instruction Pointer αυξάνεται μεχρι να φτάσει στην επόμενη οδηγία μετα από αυτήν που κάλεσε την function. Η address αυτή μετά, τοποθετείται στο stack, και πρόκειται να αποτελέσει την return address όταν η ίδια η function επιστρέψει.

2 ==> Χώρος δημιουργείται στο stack για το return type της function που έχουμε κανει Declare στον κώδικα μας (πχ αν θα επιστρεψει int,float,char κλπ). Πχ εστω οτι οτι πρόκεται να βαλουμε ενα αριθμό που πιάνει 2byte, αν όμως έχουμε θεσει return type σε Int το οποιο είναι 4byte, τα 2 επιπλεον bytes μπαίνουν κι αυτά στο stack, έστω και κενά χωρις τιμή (τα ονομαζουμε garbage) εως ότου να δώσουμε τον αριθμό/τιμή στην μεταβλητή μας.

3 ==> Η address της function που καλέσαμε, είναι φυλακισμένη σε μια ειδική περιοχή της μνήμης γι αυτό τον λόγο. Ειναι loaded στον Instruction Pointer, οπότε η επόμενη οδηγία που θα εκτελεστεί θα είναι ΜΕΣΑ στο body της Function μας.

4 ==> Η προσωρινή κορυφή του Stack είναι σημειωμένη και κρατιέται από απο εναν specia pointer που λεγεται stack frame. Ο,τιδήποτε που προσθέτεται στο stack από αυτήν την στιγμή και πέρα μέχρι να επιστρεψει η function θα λαμβάνεται ως local της function.

5 ==> Ολα τα arguments (οι τιμες δηλαδη που δωσαμε στις μεταβλητες) της function έχουν τοποθετηθεί στο Stack.

6 ==> Η οδηγεία τώρα μεσα στον Instruction Pointer εκτελείται, ετσι εκτελείται η πρώτη οδηγία που βρισκέται μέσα στην function.

7 ==> Local μεταβλητες ειναι πλεον pushed on the stack από την στιγμή που τις ορίζουμε.

Οταν η function ειναι ετοιμη να επιστρέψει, η Return value τοποθετειται στην περιοχή του stack (βλεπε βημα 2). Μετα το stack κανει τα παντα POP μεχρι τον Frame Pointer που στην ουσια κρατούσε μεχρι τωρα ολα τα local της function.

Η return value γινεται κ αυτη POP απο το stack και επιστρέφει ως τιμή της function call, η address του βηματος 1 δε φυλάσσεται πλέον. Πηγαίνει στον Instruction Pointer . Ετσι το προγραμμα επιστρέφει πισω στον κώδικα και συνεχίζει.

Τι καταλαβατε ????

Link to comment
Share on other sites

Dr. δεν καταλαβαίνω ποια είναι η ερώτηση σου (παρόλο που το link ήταν ενημερωτικότατο)... :cool:

Αν δεν απαντάω σε απόρια που ίσως έχεις πες μου...

Όσων αφορά το stack είναι απλά ένας χώρος μνήμης που λειτουργεί σαν προσωρινή μνήμη για μεταβλητές, θέσεις μνήμεις και registers... όταν έχεις μια main function και κάνεις calls σε άλλες functions, για να συνεχίσει ομαλά την λειτουργία του το πρόγραμμα θα πρέπει να αποθηκεύει το σύστημα την καταστασή του πριν το call έτσι ώστε όταν επανέλθει να "θυμάται" που ήταν και τι έκανε... σε windows programming δεν είναι τόσο σημαντικό το stack γιατί ελέγχεται εξ'ολοκλήρου από το environment της γλώσσας, αλλά για embeded εφαρμογές και low level programming γλώσσες η χρήση του stack πρέπει να γίνεται πολύ προσεκτικά καθώς υπάρχει ο κίνδυνος να κάνει overflow... το overflow στο stack γίνεται όταν αποθηκεύονται σε αυτό μεταβλητές οι οποίες δεν ανακτώνται όταν πρέπει, με αποτέλεσμα να συσσωρεύονται και να ξεπερνούν σε μέγεθος το μέγεθος του stack... επίσης αν δεν γίνει σωστή χρήση μπορεί να τραβάς λάθος μεταβλητές από το stack (σαν pointers για παράδειγμα) και να οδηγείς τον κώδικά σου σε λάθος θέσεις μνήμης ή να γράφεις πάνω σε μια θέση μνήμης που δεν πρέπει, αυτό σε οδηγεί στα exception errors...

Όπως προείπα όμως σε high level programming (visual studio π.χ.) δεν έχεις λόγο να ανησυχείς γι' αυτό καθότι το αναλαμβάνει η ίδια η γλώσσα, εκτός και αν έχει κάποιο bug... σε low level όμως (assembly, C π.χ.) είναι ευθύνη σου να ελέγχεις το stack και τους pointer σου... Γενική αρχή είναι "ότι μπαίνει πρέπει να βγαίνει και μάλιστα με την σωστή σειρά"...

Link to comment
Share on other sites

Λοιπον, θα ηθελα να μου περιγραψει καποιος τι γινεται στο Stack με τους Registers (EIP,ESP κλπ) οταν γινεται μια Call Function.

Ο λογος που το θελω αυτο ειναι να κατανοήσω τα Buffer Overflow Exploits στο Linux.

Link to comment
Share on other sites

Αρκετά ενημερωτικό είναι και αυτό το LINK...

Καταρχήν οι πιο συνήθεις λόγοι να έχεις stack overflow είναι οι παρακάτω. Το ορισμένο μέγεθος του stack είναι πολύ μικρό για την εφαρμογή και ο compliler/linker δεν ανιχνεύει από τον κώδικα ότι θα γίνει overflow... Αν το stack size δεν μπορεί να αλλάξει τότε είσαι υποχρεωμένος ο κώδικας την ώρα της λειτουργίας να μην υπερβαίνει στην χρήση του το stack (π.χ. μεγάλα strings ή arrays, ή και strings που δεν έχουν null termination... Αν όμως μπορείς να αλλάξεις το μέγεθος του stack τότε πρέπει να το προσαρμόζεις στον κώδικα σου, χωρίς όμως υπερβολές στο μέγεθος του γιατί μπορεί να έχεις bugs τα οποία είναι αόρατα... πχ να έχεις ένα πολύ μεγάλο stack 4096 bytes ενώ ο κώδικας σου δεν χρειάζεται πάνω από 256, στην περίπτωση αυτή αν έχεις ένα string το οποίο δεν είναι null terminated θα περάσει σωστά στο stack, αλλά όταν το ξανακαλέσεις να σου κάνει exception... το λάθος αυτό είναι στην ουσία αόρατο γιατί δύσκολα μπορείς να εντοπίσεις σε ποιο σημείο του κώδικα σου μπορεί να χτηπήσει και κάθε φορά που κάνεις compile έχοντας αλλάξει μόνο μια εντολή, κάθε φορά να βλέπεις αλλού το λάθος... αυτό μπορεί να σε τρελλάνει και αν είναι μεγάλος ο κώδικας 10000+ γραμμές κώδικα κάνει την ανίχνευσή του πολύ δύσκολη...

Όσων αφορά τώρα το stack και instuction pointer η συμπεριφορά τους δεν είναι προκαθορισμένη, αλλά εξαρτάται από τον ίδιο τον κώδικα, τον compiler/linker/assembler κτλ... γενικά στο overflow ο stack pointer θα βγει εκτός ορίων και ανάλογα με την πλατφόρμα μπορεί να γράψει σε θέσεις μνήμης που βρίσκονται μετά από αυτόν και να κάνει exception ή απλά να κάνει exception επειδή ο επεξεργαστής ανίχνευσε το overflow... ο instruction pointer θα δείχνει την επόμενη assembly εντολή, αν αυτός είναι corrupted και δεν γίνει stack overflow (λόγο αδυναμίας ανίχνευσής του από το σύστημα) τότε θα την εκτελέσει... κατά πάσα πιθανότοτηα όμως αυτό δεν θα είναι εντολή ή δεν θα είναι η εντολή που θα έπρεπε, οπότε πάλι θα καταλήξεις σε exception error ή άμμεσα ή μετά από μερικές εντολές... το exception μετά από λάθος EIP μπορεί να είναι οτιδήποτε και σίγουρα θα είναι τυχαίο...

Edit:

Λέγοντας ότι η συμπεριφορά του EIP και ESP δεν είναι προκαθορισμένη δεν εννοώ στον τρόπο λειτουργίας τους, ο οποίος είναι απόλυτα προκαθορισμένος και συγκεκριμένος, αλλά στο τι θα έχουν καταχωρημένο μετά από το λάθος (buffer overflow, exception κτλ)...

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.