Post

Partie 18 - Les small bins - fonctionnement et exploitation (1/2)

Les small bins : fonctionnement et exploitation (1/2)

Pour ĂȘtre honnĂȘte avec vous, j’ai longuement hĂ©sitĂ© Ă  Ă©crire les chapitres dĂ©diĂ©s aux small bins et large bins tant leur usage est marginal depuis la prĂ©sence du tcache. Mais bon, qui sait, peut-ĂȘtre qu’avoir Ă©crit quelque part la maniĂšre dont ces types de corbeilles fonctionnent sera utile un jour ou l’autre đŸ€·â€â™‚ïž.

Utilisation

Nous avons rĂ©cemment analysĂ© le fonctionnement de la unsorted bin au cours des prĂ©cĂ©dents chapitres. Vous vous rappelez sans doute qu’un bloc libre de la unsorted bin n’a qu’une seule chance d’ĂȘtre rĂ©utilisĂ©. S’il n’est pas utilisĂ© lors de la prochaine allocation (ex : taille d’allocation plus importante), alors il est insĂ©rĂ© soit dans une small bin soit dans une large bin en fonction de sa taille.

Gestion des blocs libres

Taille des blocs gérés

Agencement en mémoire

Voyons quelles sont les tailles de bloc gĂ©rĂ©es par les small bins, cela est trĂšs utile pour savoir si un bloc de la unsorted bin ira dans une small bin ou large bin. Tout d’abord, voici ce que dit le code source au sujet du nombre de small bins :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
   Indexing

    Bins for sizes < 512 bytes contain chunks of all the same size, spaced
    8 bytes apart. Larger bins are approximately logarithmically spaced:
    
    64 bins of size       8
    32 bins of size      64
    16 bins of size     512
     8 bins of size    4096
     4 bins of size   32768
     2 bins of size  262144
     1 bin  of size what's left
     
    There is actually a little bit of slop in the numbers in bin_index
    for the sake of speed. This makes no difference elsewhere.
    
    The bins top out around 1MB because we expect to service large
    requests via mmap.
    
    Bin 0 does not exist.  Bin 1 is the unordered list; if that would be
    a valid chunk size the small bins are bumped up one.
 */
 
#define NBINS             128
#define NSMALLBINS         64

Les nombres affichĂ©s dans cette section de code de la glibc peuvent prĂȘter Ă  confusion đŸ˜”â€đŸ’«, il faut le reconnaĂźtre. Nous allons dĂ©tailler Ă©tape par Ă©tape la maniĂšre de compter le nombre de small bins et large bins pour que vous compreniez comment s’effectue, au final, leur dĂ©nombrement.

Faisons la somme de toutes les corbeilles listĂ©es dans le commentaire ci-dessus : 64+32+16+8+4+2+1 = 127. Or la valeur de NBINS est 128. On s’attendrait donc Ă  ce qu’il y ait : 64 small bins et 64 large bins. Mais non, il y a une unitĂ© de diffĂ©rence đŸ€” 


La fin du commentaire stipule Bin 0 does not exist.. Vous remarquerez, d’ailleurs, que les boucles indexĂ©es par rapport Ă  NBINS dĂ©marrent toujours Ă  1, comme ici.

Bah faut savoir : soit les tableaux en informatique commencent à 0, soit à 1 😑.

Tout Ă  fait d’accord ! C’est trĂšs perturbant pour la comprĂ©hension de l’agencement des small bins et large bins. Mais bon, au moins nous comprenons pourquoi il y en a bien 127 et non 128 au total.

Mais il y a autre chose qui peut sembler Ă©trange : NSMALLBINS vaut 64. Le problĂšme ? Bah c’est que les small bins, il n’y en a pas 64 mais 62 😅. Puisque la Bin 0 n’existe pas, on passe de 64 Ă  63 corbeilles. Reste Ă  dĂ©terminer comment nous sommes passĂ©s de 63 Ă  62.

De plus, d’aprĂšs le commentaire Bin 1 is the unordered list, on en dĂ©duit que la premiĂšre corbeille n’est tout autre que la unsorted bin. Ainsi, voici comment se dĂ©composent ces 127 corbeilles :

  • 1 unsorted bin ;
  • 62 small bins ;
  • 64 large bins ;

Et la boucle est (presque) bouclĂ©e ! Il nous faut dĂ©sormais comprendre le tableau bins qui fait partie de l’arĂšne :

1
2
/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];

Normalement le tableau bins devrait vous dire quelque chose étant donné que ses deux premiers éléments pointent vers le premier et dernier bloc de la unsorted bin. Vous avez sûrement dû le voir passer en utilisant la commande arena. Pour rappel, il ressemble à :

Il est important de comprendre l’agencement des small bins et large bins dans ce tableau car ce n’est pas Ă©vident de prime abord.

DĂ©jĂ , calculons sa taille : NBINS * 2 - 2 = 128 * 2 - 2 = 254. 254 c’est aussi 127*2. Cette prĂ©cision n’est pas anodine. Vu que chaque corbeille “classique” (unsorted bin, small bin et large bin) est une liste doublement chaĂźnĂ©e, l’arĂšne a besoin d’avoir un pointeur vers son premier et son dernier Ă©lĂ©ment. C’est pourquoi la glibc utilise un tableau de 127*2 Ă©lĂ©ments pour reprĂ©senter les 127 corbeilles.

Le tableau ci-dessous synthĂ©tise la correspondance entre le numĂ©ro d’une corbeille (qui va de 1 Ă  127) et ses deux index dans le tableau bins :

Numéro de la corbeilleSes deux index dans binsType de la corbeille
1{ 0 ; 1 }unsorted bin
2{ 2 ; 3 }small bin
3{ 4 ; 5 }small bin
n{ n*2 - 2 ; n*2 - 1 }small bins
63
{ 0x7c ; 0x7d } ({ 124 ; 125 })
small bin
64{ 0x7e ; 0x7f } ({ 126 ; 127 })large bin
65{ 0x80 ; 0x81 } ({ 128 ; 129 })large bin
n{ n*2 - 2 ; n*2 - 1 }large bins
127{ 0xfc ; 0xfd } ({ 252 ; 253 })large bin

Pfiouu đŸ„” ! Au moins maintenant, nous savons exactement oĂč est stockĂ©e chaque corbeille et la maniĂšre dont se calculent les indices.

Résumé

Les tailles données ci-dessous incluent les métadonnées.

À partir du nombre de small bins et de l’espace entre deux small bins en fonction de l’architecture, nous obtenons les rĂ©sultats suivants :

Version de la libc <= 2.25
VersionEspace entre deux corbeillesNombre de small binsTaille minTaille max (incluse)
32 bits8 octets620x100x1f8
64 bits0x10 octets620x200x3f0
Version de la libc > 2.25
VersionEspace entre deux corbeillesNombre de small binsTaille minTaille max (incluse)
32 bits0x10 octets620x200x3e0
64 bits0x10 octets620x200x3f0

Pour rappel, dans les anciennes versions de la glibc (≀ 2.25 (2017)), la contrainte d’alignement des blocs est de 8 octets. De ce fait, vous remarquerez qu’aprĂšs la version 2.25, les intervalles de taille en 32 bits et 64 bits ne diffĂšrent pas tellement.

Les blocs non rĂ©utilisĂ©s de la unsorted bin vont dans une large bin lorsque leur taille ne leur permet pas d’ĂȘtre traitĂ©s par une small bin ?

Exactement !

Organisation des corbeilles

Concernant la structure des corbeilles, ce sera assez facile à comprendre étant donné que cela est trÚs proche de la structure de la unsorted bin :

  • les blocs sont gĂ©rĂ©s via un systĂšme de liste doublement chaĂźnĂ©e circulaire ;
  • la liste est de type FIFO (First In First Out). Autrement dit le premier bloc arrivĂ©, est le premier servi.

L’une des particularitĂ©s de ce type de corbeilles est qu’un bloc qui vient d’ĂȘtre libĂ©rĂ© ne va jamais directement dans une small bin mais passe dans un premier temps par la unsorted bin. Si ce bloc n’est pas rĂ©utilisĂ© et qu’il a une petite taille, il va dans la small bin idoine.

Transition de la unsorted bin vers une small bin

Cas n°1 : un seul bloc

Comme Ă  l’accoutumĂ©e, je vous propose de voir avec un schĂ©ma ce qui se passe lorsqu’un bloc n’est pas rĂ©utilisĂ© dans la unsorted bin. Prenons comme support le programme suivant :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "stdlib.h"

// Version de la glibc : 2.24-9ubuntu2_amd64
int main()
{
    void *a = malloc(0x500);
    malloc(1);
    
    free(a); // Bloc A -> unsorted bin (taille = 0x500)
    malloc(0x500 - 0x100); // Bloc A reste dans unsorted bin (taille = 0x100)
    malloc(0x200); // 0x200 > 0x100 => Bloc A -> small bin (taille = 0x100)

	return 0;
}

Ci-dessous, les diffĂ©rentes Ă©tapes d’exĂ©cution du programme :

  1. le bloc A de 0x500 octets est allouĂ© ainsi qu’un autre petit bloc afin d’éviter la consolidation avec le bloc du sommet lors de la libĂ©ration ;
  2. free(a) : Ă©tant donnĂ© que dans cette version de la glibc (2.24) il n’y a pas de tcache et que la taille de ce bloc n’est pas gĂ©rĂ©e par une fastbin, le bloc libre est gĂ©rĂ© par la unsorted bin ;
  3. malloc(0x500 - 0x100) : un bloc de 0x400 octets est allouĂ©. Sachant que le bloc libre A a Ă©tĂ© rĂ©utilisĂ©, le reste du bloc (0x100 octets) ne quitte pas la unsorted bin. De nouveau, le bloc libre de 0x100 octets a une seule chance d’ĂȘtre rĂ©utilisĂ© ;
  4. malloc(0x200) : la taille de l’allocation demandĂ©e dĂ©passe celle du bloc libre, ce dernier est donc placĂ© dans la small bin de taille 0x100.
Cas n°2 : plusieurs blocs

Analysons désormais le comportement du programme suivant :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include "stdlib.h"

// Version de la glibc : 2.24-9ubuntu2_amd64
int main()
{
    void *a = malloc(0x500);
    malloc(1);

    void *b = malloc(0x500);
    malloc(1);

    void *c = malloc(0x500);
    malloc(1);


    free(a); // Bloc A -> unsorted bin (taille = 0x500)
    malloc(0x500 - 0x100); // Bloc A reste dans unsorted bin (taille = 0x100)
    malloc(0x200); // 0x200 > 0x100 => Bloc A -> small bin (taille = 0x100)

    free(b); // Bloc B -> unsorted bin (taille = 0x500)
    malloc(0x500 - 0x100); // Bloc B reste dans unsorted bin (taille = 0x100)
    malloc(0x200); // 0x200 > 0x100 => Bloc B -> small bin (taille = 0x100)

    free(c); // Bloc B -> unsorted bin (taille = 0x500)
    malloc(0x500 - 0x100); // Bloc B reste dans unsorted bin (taille = 0x100)
    malloc(0x200); // 0x200 > 0x100 => Bloc B -> small bin (taille = 0x100)

	return 0;
}

Il s’agit du mĂȘme programme que le prĂ©cĂ©dent, si ce n’est qu’à la fin de son exĂ©cution, 3 blocs libres seront prĂ©sents dans la small bin de taille 0x100.

Petite question pour voir si vous avez bien compris : quel est le numĂ©ro de la small bin qui gĂšre les blocs libres de taille 0x100 ? Quels sont ses deux index dans le membre (tableau) bins de l’arĂšne ?

Réponse : aWwgcydhZ2l0IGRlIGxhIHNtYWxsIGJpbiBuwrAxNiBkb250IGxlcyBkZXV4IGluZGV4IHNvbnQgeyAweDFlIDsgMHgxZn0u

En exĂ©cutant le programme jusqu’au retour du main dans gdb, nous pouvons effectivement constater la prĂ©sence des trois blocs libres de 0x100 octets dans une small bin :

Selon la doc’ de la glibc qui stipule que la premiĂšre bin (n°1) est la unsorted bin, le numĂ©ro de cette small bin est 16 parmi l’ensemble des trois corbeilles : unsorted bin + small bins + large bins.

Par contre, parmi toutes les small bins, celle qui gĂšre les blocs de taille 0x100 est la numĂ©ro 15. D’oĂč la valeur idx=15 utilisĂ©e dans gdb.

En gros : c’est la 16e bin (unsorted bin, small bins et large bins confondues) et la 15e small bin parmi les small bins.

Oui, c’est trĂšs pĂ©nible de devoir jongler entre diffĂ©rentes maniĂšres d’indexer les corbeilles 😼‍💹 
 Ce qui est important est de se rappeler l’origine de ces diffĂ©rences.

En utilisant la commande arena, nous trouvons rapidement les indices de cette small bin :

En s’intĂ©ressant aux liens entre les diffĂ©rents blocs, vous constaterez que les blocs libres d’un small bin sont liĂ©s de la mĂȘme maniĂšre que des blocs libres de la unsorted bin :

Les listes doublement chaßnées circulaires sont un peu difficiles à lire au début, mais avec un peu de temps, on finit par en saisir la logique.

Structure et mĂ©tadonnĂ©es d’un bloc issu d’une small bin

Ce qui est, au dĂ©part, compliquĂ© avec les small bins, c’est le nombre de corbeilles qu’il y a et la maniĂšre dont elles sont indexĂ©es et gĂ©rĂ©es par l’arĂšne. Quant Ă  l’organisation des blocs au sein d’une corbeille, c’est semblable Ă  ce qui est effectuĂ© dans la unsorted bin.

D’ailleurs, mĂȘme la structure d’un bloc dans une small bin est similaire Ă  celle des blocs de la unsorted bin :

On passe Ă  la suite ?

This post is licensed under CC BY-NC 4.0 by the author.