En utilisant cet exemple provenant de wikipedia, dans lequel DrawSquare () appelle DrawLine (),
(Notez que ce diagramme a des adresses hautes en bas et des adresses basses en haut.)
Quelqu'un pourrait-il m'expliquer quoi ebp
et esp
sont dans ce contexte?
D'après ce que je vois, je dirais que le pointeur de la pile pointe toujours vers le haut de la pile et le pointeur de base vers le début de la fonction actuelle? Ou quoi?
edit: je veux dire cela dans le contexte des programmes Windows
edit2: Et comment ça eip
marche aussi?
edit3: J'ai le code suivant de MSVC ++:
var_C= dword ptr -0Ch
var_8= dword ptr -8
var_4= dword ptr -4
hInstance= dword ptr 8
hPrevInstance= dword ptr 0Ch
lpCmdLine= dword ptr 10h
nShowCmd= dword ptr 14h
Tous semblent être des dwords, prenant ainsi 4 octets chacun. Je peux donc voir qu'il y a un écart entre hInstance et var_4 de 4 octets. Que sont-ils? Je suppose que c'est l'adresse de retour, comme on peut le voir sur l'image de wikipedia?
(NDLR: supprimé une longue citation de la réponse de Michael, qui n'appartient pas à la question, mais une question de suivi a été modifiée dans):
En effet, le flux de l'appel de fonction est le suivant:
* Push parameters (hInstance, etc.)
* Call function, which pushes return address
* Push ebp
* Allocate space for locals
Ma question (enfin, j'espère!) Est maintenant: qu'est-ce qui se passe exactement à partir du moment où j'éclate les arguments de la fonction que je veux appeler jusqu'à la fin du prologue? Je veux savoir comment l'ebp, esp évolue pendant ces moments (j'ai déjà compris comment fonctionne le prologue, je veux juste savoir ce qui se passe après avoir poussé les arguments sur la pile et avant le prologue).
Réponses:
esp
est comme vous le dites, le haut de la pile.ebp
est généralement défini suresp
au début de la fonction. Les paramètres de fonction et les variables locales sont accessibles en ajoutant et en soustrayant, respectivement, un décalage constant deebp
. Toutes les conventions d'appel x86 se définissentebp
comme étant préservées sur les appels de fonction.ebp
lui-même pointe en fait sur le pointeur de base de l'image précédente, ce qui permet à la pile de marcher dans un débogueur et de visualiser les autres variables locales des images pour fonctionner.La plupart des prologues de fonction ressemblent à ceci:
Plus tard dans la fonction, vous pouvez avoir du code comme (en supposant que les deux variables locales sont de 4 octets)
L' optimisation des omissions FPO ou du pointeur de trame que vous pouvez activer éliminera cela et l'utilisera
ebp
comme un autre registre et accèdera directement aux locauxesp
, mais cela rend le débogage un peu plus difficile car le débogueur ne peut plus accéder directement aux trames de pile des appels de fonction précédents.ÉDITER:
Pour votre question mise à jour, les deux entrées manquantes dans la pile sont:
En effet, le flux de l'appel de fonction est le suivant:
hInstance
, etc.)ebp
la source
ESP est le pointeur de pile actuel, qui changera chaque fois qu'un mot ou une adresse est poussé ou sauté dans / hors de la pile. EBP est un moyen plus pratique pour le compilateur de garder une trace des paramètres d'une fonction et des variables locales que d'utiliser directement l'ESP.
Généralement (et cela peut varier d'un compilateur à l'autre), tous les arguments d'une fonction appelée sont poussés sur la pile par la fonction appelante (généralement dans l'ordre inverse de leur déclaration dans le prototype de la fonction, mais cela varie) . Ensuite, la fonction est appelée, ce qui pousse l'adresse de retour (EIP) sur la pile.
Lors de l'entrée dans la fonction, l'ancienne valeur EBP est poussée sur la pile et EBP est définie sur la valeur ESP. Ensuite, l'ESP est décrémenté (car la pile croît vers le bas en mémoire) pour allouer de l'espace aux variables locales et temporaires de la fonction. À partir de ce moment, pendant l'exécution de la fonction, les arguments de la fonction sont situés sur la pile à des décalages positifs d'EBP (car ils ont été poussés avant l'appel de fonction), et les variables locales sont situées à des décalages négatifs d'EBP (car ils ont été alloués sur la pile après l'entrée de la fonction). C'est pourquoi l'EBP est appelé le pointeur de trame , car il pointe vers le centre de la trame d'appel de fonction .
À la sortie, tout ce que la fonction a à faire est de régler ESP sur la valeur d'EBP (qui désalloue les variables locales de la pile et expose l'entrée EBP en haut de la pile), puis de faire sortir l'ancienne valeur EBP de la pile, puis la fonction retourne (insertion de l'adresse de retour dans EIP).
En revenant à la fonction appelante, il peut ensuite incrémenter ESP afin de supprimer les arguments de fonction qu'il a poussés sur la pile juste avant d'appeler l'autre fonction. À ce stade, la pile est de retour dans le même état qu'elle était avant d'appeler la fonction appelée.
la source
Vous avez raison. Le pointeur de pile pointe vers l'élément supérieur de la pile et le pointeur de base pointe vers le haut "précédent" de la pile avant l'appel de la fonction.
Lorsque vous appelez une fonction, toute variable locale sera stockée sur la pile et le pointeur de pile sera incrémenté. Lorsque vous revenez de la fonction, toutes les variables locales de la pile sortent du domaine. Pour ce faire, redéfinissez le pointeur de pile sur le pointeur de base (qui était le haut "précédent" avant l'appel de fonction).
Faire l'allocation de mémoire de cette façon est très , très rapide et efficace.
la source
EDIT: Pour une meilleure description, voir Désassemblage / Fonctions x86 et Cadres de pile dans un WikiBook sur l'assemblage x86. J'essaie d'ajouter des informations qui pourraient vous intéresser à l'aide de Visual Studio.
Le stockage de l'EBP de l'appelant en tant que première variable locale est appelé un cadre de pile standard, et cela peut être utilisé pour presque toutes les conventions d'appel sous Windows. Il existe des différences selon que l'appelant ou l'appelé désalloue les paramètres passés et quels paramètres sont passés dans les registres, mais ceux-ci sont orthogonaux au problème de trame de pile standard.
En parlant de programmes Windows, vous pourriez probablement utiliser Visual Studio pour compiler votre code C ++. Sachez que Microsoft utilise une optimisation appelée Frame Pointer Omission, qui rend presque impossible de parcourir la pile sans utiliser la bibliothèque dbghlp et le fichier PDB pour l'exécutable.
Cette omission de pointeur de trame signifie que le compilateur ne stocke pas l'ancien EBP sur un emplacement standard et utilise le registre EBP pour autre chose, par conséquent, vous avez du mal à trouver l'EIP de l'appelant sans savoir de combien d'espace les variables locales ont besoin pour une fonction donnée. Bien sûr, Microsoft fournit une API qui vous permet de faire des cheminements de pile, même dans ce cas, mais la recherche de la base de données de la table des symboles dans les fichiers PDB prend trop de temps pour certains cas d'utilisation.
Pour éviter FPO dans vos unités de compilation, vous devez éviter d'utiliser / O2 ou ajouter explicitement / Oy- aux indicateurs de compilation C ++ dans vos projets. Vous établissez probablement un lien avec le runtime C ou C ++, qui utilise FPO dans la configuration Release, vous aurez donc du mal à effectuer des parcours de pile sans dbghlp.dll.
la source
Tout d'abord, le pointeur de pile pointe vers le bas de la pile, car les piles x86 sont construites à partir de valeurs d'adresse élevées vers des valeurs d'adresse inférieures. Le pointeur de pile est le point où le prochain appel à pousser (ou appeler) placera la valeur suivante. Son fonctionnement est équivalent à l'instruction C / C ++:
Le pointeur de base est en haut de l'image actuelle. ebp indique généralement votre adresse de retour. ebp + 4 pointe vers le premier paramètre de votre fonction (ou la valeur this d'une méthode de classe). ebp-4 pointe vers la première variable locale de votre fonction, généralement l'ancienne valeur d'ebp afin que vous puissiez restaurer le pointeur de trame précédent.
la source
Cela fait longtemps que je n'ai pas fait de programmation d'assemblage, mais ce lien pourrait être utile ...
Le processeur possède une collection de registres qui sont utilisés pour stocker des données. Certains d'entre eux sont des valeurs directes tandis que d'autres pointent vers une zone dans la RAM. Les registres ont tendance à être utilisés pour certaines actions spécifiques et chaque opérande de l'assemblage nécessitera une certaine quantité de données dans des registres spécifiques.
Le pointeur de pile est principalement utilisé lorsque vous appelez d'autres procédures. Avec les compilateurs modernes, un tas de données sera d'abord vidé sur la pile, suivi de l'adresse de retour pour que le système sache où retourner une fois qu'on lui a dit de retourner. Le pointeur de pile pointera à l'emplacement suivant où les nouvelles données peuvent être poussées vers la pile, où elles resteront jusqu'à ce qu'elles soient à nouveau renvoyées.
Les registres de base ou les registres de segments pointent simplement vers l'espace d'adressage d'une grande quantité de données. Combiné avec un deuxième enregistreur, le pointeur de base divisera la mémoire en blocs énormes tandis que le deuxième registre pointera sur un élément de ce bloc. Des pointeurs de base pointent donc vers la base de blocs de données.
Gardez à l'esprit que l'assemblage est très spécifique au processeur. La page à laquelle j'ai lié fournit des informations sur les différents types de CPU.
la source
Edit Ouais, c'est surtout faux. Il décrit quelque chose de complètement différent au cas où quelqu'un serait intéressé :)
Oui, le pointeur de pile pointe vers le haut de la pile (que ce soit le premier emplacement de pile vide ou le dernier plein dont je ne suis pas sûr). Le pointeur de base pointe vers l'emplacement de mémoire de l'instruction en cours d'exécution. C'est au niveau des opcodes - l'instruction la plus élémentaire que vous pouvez obtenir sur un ordinateur. Chaque opcode et ses paramètres sont stockés dans un emplacement mémoire. Une ligne C ou C ++ ou C # peut être traduite en un opcode, ou une séquence de deux ou plus selon sa complexité. Ceux-ci sont écrits dans la mémoire du programme de manière séquentielle et exécutés. Dans des circonstances normales, le pointeur de base est incrémenté d'une instruction. Pour le contrôle de programme (GOTO, IF, etc.), il peut être incrémenté plusieurs fois ou simplement remplacé par l'adresse de mémoire suivante.
Dans ce contexte, les fonctions sont stockées dans la mémoire programme à une certaine adresse. Lorsque la fonction est appelée, certaines informations sont poussées sur la pile qui permet au programme de retrouver son origine d'où la fonction a été appelée ainsi que les paramètres de la fonction, puis l'adresse de la fonction dans la mémoire du programme est poussée dans le pointeur de base. Au cycle d'horloge suivant, l'ordinateur commence à exécuter des instructions à partir de cette adresse mémoire. Puis, à un moment donné, il RETOURNE à l'emplacement de mémoire APRÈS l'instruction qui a appelé la fonction et continue à partir de là.
la source
esp signifie "Extended Stack Pointer" ..... ebp pour "Something Base Pointer" .... et eip pour "Something Instruction Pointer" ...... Le pointeur de pile pointe vers l'adresse de décalage du segment de pile . Le pointeur de base pointe vers l'adresse de décalage du segment supplémentaire. Le pointeur d'instruction pointe vers l'adresse de décalage du segment de code. Maintenant, à propos des segments ... ce sont de petites divisions de 64 Ko de la zone de mémoire du processeur ..... Ce processus est connu sous le nom de segmentation de la mémoire. J'espère que ce message a été utile.
la source