- Prenons le code suivant :
- int main(int argc, char* argv[])
- {
- printf("nombre d'arguments = %d\n", argc);// %d attend un entier ... on lui donne
- return 0;
- }
-
- le résultat est évident .
-
- De même pour celui ci :
- int main(int argc, char* argv[])
- {
- printf("nom du programme = %s\n", argv[0]);// %s attend une chaine ... on lui donne
- return 0;
- }
-
- Et le suivant :
- int main(int argc, char* argv[])
- {
- printf("adresse du 1er argument = %p\n", argv[0]);// %p attend un pointeur... on lui donne
- return 0;
- }
-
- mais que pensez vous de celui là ?
- int main(int argc, char* argv[])
- {
- printf("pile:\n%p\n%p\n%p\n%p\n%p\n%p\n\n");// ... on ne lui donne rien
- return 0;
- }
-
- nous avons "oublié" les arguments
- Que fait la fonction printf à votre avis ?
- Elle plante ?
- Pas du tout, elle nous renvoie le contenu de la PILE
-
- Lorsque nous passons des arguments à une fonction, le compilateur génère le code nécessaire (on appelle cela le prologue) pour empiler ces arguments sur ... la pile.
- Lorsque l'on demande à printf d'écrire un entier, nous lui passons généralement l'entier. Celui ci est empilé et la fonction n'a qu'a le dépiler.
- Elle travaille donc normalement lorsque nous oublions de lui passer les arguments et elle nous fournit le contenu de sa pile.
-
- Bien sûr, il y a plus simple pour voir la pile : l'utilisation d'un débogueur.
- Pour s'en convaincre, utiliser la fenêtre "memory" sous Visual Studio
- Remarque : attention aux options de compilations
- Il est bon de désactiver les options d'optimisation pour "comprendre la pile"
- Les conventions d'appel sont importantes aussi : par défaut __cdecl (/Gd)
-
- Autre moyen :
- utiliser l'adresse ESP et un peu d'assembleur __asm { mov p, esp}
- ainsi :
-
- int main(int argc, char* argv[])
- {
- int * p =0;
- __asm { mov p, esp}
- printf("adresse de pile = %p\n", p);
- printf("contenu de pile :\nAdresse\t\tContenu\n");
- int * pFin = p-5;
- for (;p>pFin; p--)
- printf("%p\t%08X\n", p, *p);
- return 0;
- }
Prenons le code suivant :
int main(int argc, char* argv[])
{
printf("nombre d'arguments = %d\n", argc);// %d attend un entier ... on lui donne
return 0;
}
le résultat est évident .
De même pour celui ci :
int main(int argc, char* argv[])
{
printf("nom du programme = %s\n", argv[0]);// %s attend une chaine ... on lui donne
return 0;
}
Et le suivant :
int main(int argc, char* argv[])
{
printf("adresse du 1er argument = %p\n", argv[0]);// %p attend un pointeur... on lui donne
return 0;
}
mais que pensez vous de celui là ?
int main(int argc, char* argv[])
{
printf("pile:\n%p\n%p\n%p\n%p\n%p\n%p\n\n");// ... on ne lui donne rien
return 0;
}
nous avons "oublié" les arguments
Que fait la fonction printf à votre avis ?
Elle plante ?
Pas du tout, elle nous renvoie le contenu de la PILE
Lorsque nous passons des arguments à une fonction, le compilateur génère le code nécessaire (on appelle cela le prologue) pour empiler ces arguments sur ... la pile.
Lorsque l'on demande à printf d'écrire un entier, nous lui passons généralement l'entier. Celui ci est empilé et la fonction n'a qu'a le dépiler.
Elle travaille donc normalement lorsque nous oublions de lui passer les arguments et elle nous fournit le contenu de sa pile.
Bien sûr, il y a plus simple pour voir la pile : l'utilisation d'un débogueur.
Pour s'en convaincre, utiliser la fenêtre "memory" sous Visual Studio
Remarque : attention aux options de compilations
Il est bon de désactiver les options d'optimisation pour "comprendre la pile"
Les conventions d'appel sont importantes aussi : par défaut __cdecl (/Gd)
Autre moyen :
utiliser l'adresse ESP et un peu d'assembleur __asm { mov p, esp}
ainsi :
int main(int argc, char* argv[])
{
int * p =0;
__asm { mov p, esp}
printf("adresse de pile = %p\n", p);
printf("contenu de pile :\nAdresse\t\tContenu\n");
int * pFin = p-5;
for (;p>pFin; p--)
printf("%p\t%08X\n", p, *p);
return 0;
}