Analyse du flux de trame de pile lors des appels de fonction

Analyse d'un processus de saut simple

  1. Code source

#include <stdio.h>
int difference(int x, int y) {
  return x - y;
}
int total(int val1, int val2) {
   int resultat = difference(100, 9);
   return val1 + val2 + resultat;
}

int principal(void) {
   int premiere = 12;
   int seconde = 98;
   int total_resultat = total(premiere, seconde);
   return 0;
}

  1. Code assembleur après compilation

0x555555555129 <difference>       endbr64         
0x55555555512d <difference+4>     push   %rbp     
0x55555555512e <difference+5>     mov    %rsp,%rbp
0x555555555131 <difference+8>     mov    %edi,-0x4(%rbp)
0x555555555134 <difference+11>    mov    %esi,-0x8(%rbp)
0x555555555137 <difference+14>    mov    -0x4(%rbp),%eax
0x55555555513a <difference+17>    sub    -0x8(%rbp),%eax
0x55555555513d <difference+20>    pop    %rbp     
0x55555555513e <difference+21>    retq            
   
0x55555555513f <total>       endbr64
0x555555555143 <total+4>     push   %rbp             
0x555555555144 <total+5>     mov    %rsp,%rbp        
0x555555555147 <total+8>     sub    $0x18,%rsp       
0x55555555514b <total+12>    mov    %edi,-0x14(%rbp) 
0x55555555514e <total+15>    mov    %esi,-0x18(%rbp) 
0x555555555151 <total+18>    mov    $0x9,%esi        
0x555555555156 <total+23>    mov    $0x64,%edi       
0x55555555515b <total+28>    callq  0x555555555129 <difference>   
0x555555555160 <total+33>    mov    %eax,-0x4(%rbp)  
0x555555555163 <total+36>    mov    -0x14(%rbp),%edx 
0x555555555166 <total+39>    mov    -0x18(%rbp),%eax 
0x555555555169 <total+42>    add    %eax,%edx        
0x55555555516b <total+44>    mov    -0x4(%rbp),%eax  
0x55555555516e <total+47>    add    %edx,%eax        
0x555555555170 <total+49>    leaveq                  
0x555555555171 <total+50>    retq
   
0x555555555172 <principal>      endbr64         
0x555555555176 <principal+4>    push   %rbp     
0x555555555177 <principal+5>    mov    %rsp,%rbp
0x55555555517a <principal+8>    sub    $0x10,%rsp     
0x55555555517e <principal+12>   movl   $0xc,-0xc(%rbp)
0x555555555185 <principal+19>   movl   $0x62,-0x8(%rbp)                         
0x55555555518c <principal+26>   mov    -0x8(%rbp),%edx
0x55555555518f <principal+29>   mov    -0xc(%rbp),%eax
0x555555555192 <principal+32>   mov    %edx,%esi
0x555555555194 <principal+34>   mov    %eax,%edi
0x555555555196 <principal+36>   callq  0x55555555513f <total>                     
0x55555555519b <principal+41>   mov    %eax,-0x4(%rbp)
0x55555555519e <principal+44>   mov    $0x0,%eax
0x5555555551a3 <principal+49>   leaveq          
0x5555555551a4 <principal+50>   retq

  1. Description des registres

  • %rsp : Pointeur de pile, pointant vers le sommet de la trame de pile de la fonction actuelle (position la plus basse)
  • rip: Pointe vers l'instruction actuellement en cours d'exécution
  • %eax: La valeur de retour doit être stockée dans %eax (convention)
  1. Description des instructions

  • endbr64: Peut être ignoré, cette instruction ne modifie aucune donnée
  • push %rbp: Diminue [%rsp], puis stocke la valeur dans [%rsp]
[%rsp] = [%rsp] - 8
[%rsp] = [%rbp]


Note : %rsp stocke une adresse de pile, donc ici nous ne stockons pas la valeur dans le registre, mais dans l'adresse de pile pointée par %rsp

  • mov src dest: [dest] = [src]
  • movl $0xc,-0xc(%rbp) : Stocke un immédiat dans la pile
  • callq total: Saut, stocke l'adrsese de l'instruction suivante sur la pile, puis saute pour continuer l'exécution
[%rsp] = [%rsp] - 8
[%rsp] = adresse de la prochaine instruction, ici 0x55555555519b
[%rip] = 0x55555555513f (adresse de la fonction total, saut vers total)


  • pop %rbp:
[%rbp] = [%rsp]
[%rsp] = [%rsp] + 8


  • retq:
popq %rip:
	[%rip] = [%rsp]
	[%rsp] = [%rsp] + 8


  • leaveq:
mov  %rbp, %rsp
popq %rbp


  1. Analyse du code

Supposons que rsp = X au départ

principal()

  1. Stocke la base de la trame de pile de la fonction appelant principal dans la pile
push %rbp:
	%rsp = %rsp-8 = X-8
	[%rsp] = [%rbp]


  1. Définit l'adresse de base de la trame de pile actuelle pour rbp
mov %rsp,%rbp


  1. Crée la trame de pile pour la fonction actuelle, rsp pointant vers le sommet de la trame
sub $0x10,%rsp
	rsp = rsp - 16 # Crée une trame de pile de 16B


  1. Les quatre lignes suivnates créent les variables, car on ne peut pas placer directement des nombres dans les registres, il faut utilsier la pile
  2. Les deux lignes suivantes placent les variables dans les registres de passage de paramètres spécifiés
  3. Stocke l'adresse de retour d'exécution dans la pile, fait pointer rip vers total, et effectue le saut
callq  0x55555555513f <total>
	[rsp] = [rsp] - 8
	[rsp] = adresse de la prochaine instruction, ici 0x5555,5555,519b
	[rip] = adresse de la fonction total, saut vers total


Trame de pile actuelle

total()

Similaire à la fonction principale précédemment, stocke la base de la trame de pile de la fonction précédente, crée une trame de pile, extrait les paramètres passés par la fonction précédente, les place dans la pile, exécute, puis saute à la fonction difference

Trame de pile actuelle

difference()

Similaire auparavant, commençons l'explication à partir de pop %rbp

Trame de pile actuelle comme suit

  1. pop %rbp
[%rbp] = [%rsp]
[%rsp] = [%rsp] + 8


À ce stade, %rbp pointe vers rbp_total, %rsp pointe vers l'adresse de retour de total 2. retq

[%rip] = [%rsp]
[%rsp] = [%rsp] + 8


À ce stade, rip pointe vers adresse de retour de total, rsp pointe vers le sommet de la pile de la fonction difference

Saut de retour vers total()

total()

Comme difference n'a pas créé de trame de pile, le retour est plus simple. Lorsqu'une trame de pile est créée, l'instruction leaveq est nécessaire pour effectuer le retour

  1. leaveq
1.mov %rbp, %rsp:
		%rsp = %rbp
2.popq %rbp:
		[%rbp] = [%rsp]
		[%rsp] = [%rsp] + 8


À ce stade, %rbp pointe vers rbp_principal, rsp pointe vers l'adresse de retour de principal2. retq

[%rip] = [%rsp]
[%rsp] = [%rsp] + 8


À ce stade, rip pointe vers adresse de retour de principal, rsp pointe vers le sommet de la pile de la fonction total

Saut de retour vers principal()

Note:

Vous pouvez compiler et exécuter manuellement pour observer

Manuel d'utilisation simple de gdb

https://www.cnblogs.com/INnoVationv2/p/17517531.html

Étiquettes: pile d'appels programmation assembleur trame de pile x86_64

Publié le 22 juin à 20h24