Para melhorar a eficiência de como os pedaços são armazenados, cada pedaço não está apenas em uma lista encadeada, mas existem vários tipos. Estes são os bins e existem 5 tipos de bins: 62 small bins, 63 large bins, 1 unsorted bin, 10 fast bins e 64 tcache bins por thread.
O endereço inicial para cada bin desordenado, pequeno e grande está dentro do mesmo array. O índice 0 não é usado, 1 é o bin desordenado, os bins 2-64 são os bins pequenos e os bins 65-127 são os bins grandes.
Bins Tcache (Cache por Thread)
Mesmo que as threads tentem ter seu próprio heap (veja Arenas e Subheaps), há a possibilidade de que um processo com muitas threads (como um servidor web) acabe compartilhando o heap com outras threads. Nesse caso, a solução principal é o uso de lockers, que podem desacelerar significativamente as threads.
Quando um thread libera um pedaço, se não for muito grande para ser alocado no tcache e o bin tcache respectivo não estiver cheio (já com 7 pedaços), ele será alocado lá. Se não puder ir para o tcache, precisará esperar pelo bloqueio do heap para poder realizar a operação de liberação globalmente.
Quando um pedaço é alocado, se houver um pedaço livre do tamanho necessário no Tcache, ele o usará, caso contrário, precisará esperar pelo bloqueio do heap para poder encontrar um nos bins globais ou criar um novo.
Há também uma otimização, nesse caso, enquanto tiver o bloqueio do heap, o thread preencherá seu Tcache com pedaços do heap (7) do tamanho solicitado, para que, caso precise de mais, os encontre no Tcache.
Adicionar um exemplo de pedaço tcache
```c #include #include
int main(void) { char *chunk; chunk = malloc(24); printf("Address of the chunk: %p\n", (void *)chunk); gets(chunk); free(chunk); return 0; }
Compile-o e depure-o com um breakpoint no opcode ret da função main. então com o gef você pode ver o tcache bin em uso:
```bash
gef➤ heap bins
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
Tcachebins[idx=0, size=0x20, count=1] ← Chunk(addr=0xaaaaaaac12a0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
Estruturas e Funções do Tcache
No código a seguir, é possível ver o número máximo de bins e chunks por índice, a struct tcache_entry criada para evitar frees duplos e tcache_perthread_struct, uma struct que cada thread usa para armazenar os endereços de cada índice do bin.
tcache_entry e tcache_perthread_struct
```c // From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c
/* We want 64 entries. This is an arbitrary limit, which tunables can reduce. */
/* With rounding and alignment, the bins are... idx 0 bytes 0..24 (64-bit) or 0..12 (32-bit) idx 1 bytes 25..40 or 13..20 idx 2 bytes 41..56 or 21..28 etc. */
/* This is another arbitrary limit, which tunables can change. Each tcache bin will hold at most this number of chunks. */
define TCACHE_FILL_COUNT 7
/* Maximum chunks in tcache bins for tunables. This value must fit the range of tcache->counts[] entries, else they may overflow. */
define MAX_TCACHE_COUNT UINT16_MAX
[...]
typedef struct tcache_entry { struct tcache_entry next; / This field exists to detect double frees. */ uintptr_t key; } tcache_entry;
/* There is one of these for each thread, which contains the per-thread cache (hence "tcache_perthread_struct"). Keeping overall size low is mildly important. Note that COUNTS and ENTRIES are redundant (we could have just counted the linked list each time), this is for performance reasons. */ typedef struct tcache_perthread_struct { uint16_t counts[TCACHE_MAX_BINS]; tcache_entry *entries[TCACHE_MAX_BINS]; } tcache_perthread_struct;
</details>
A função `__tcache_init` é a função que cria e aloca o espaço para o objeto `tcache_perthread_struct`
```c
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L3241C1-L3274C2
static void
tcache_init(void)
{
mstate ar_ptr;
void *victim = 0;
const size_t bytes = sizeof (tcache_perthread_struct);
if (tcache_shutting_down)
return;
arena_get (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
if (!victim && ar_ptr != NULL)
{
ar_ptr = arena_get_retry (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
}
if (ar_ptr != NULL)
__libc_lock_unlock (ar_ptr->mutex);
/* In a low memory situation, we may not be able to allocate memory
- in which case, we just keep trying later. However, we
typically do this very early, so either there is sufficient
memory, or there isn't enough memory to do non-trivial
allocations anyway. */
if (victim)
{
tcache = (tcache_perthread_struct *) victim;
memset (tcache, 0, sizeof (tcache_perthread_struct));
}
}
Índices Tcache
O tcache possui vários bins dependendo do tamanho e os ponteiros iniciais para o primeiro chunk de cada índice e a quantidade de chunks por índice estão localizados dentro de um chunk. Isso significa que ao localizar o chunk com essas informações (geralmente o primeiro), é possível encontrar todos os pontos iniciais do tcache e a quantidade de chunks do Tcache.
Bins Rápidos
Os bins rápidos são projetados para acelerar a alocação de memória para pequenos chunks mantendo chunks recentemente liberados em uma estrutura de acesso rápido. Esses bins usam uma abordagem Last-In, First-Out (LIFO), o que significa que o chunk mais recentemente liberado é o primeiro a ser reutilizado quando há uma nova solicitação de alocação. Esse comportamento é vantajoso para a velocidade, pois é mais rápido inserir e remover do topo de uma pilha (LIFO) em comparação com uma fila (FIFO).
Além disso, os bins rápidos usam listas encadeadas simples, não duplamente encadeadas, o que melhora ainda mais a velocidade. Como os chunks nos bins rápidos não são mesclados com vizinhos, não há necessidade de uma estrutura complexa que permita a remoção do meio. Uma lista encadeada simples é mais simples e rápida para essas operações.
Basicamente, o que acontece aqui é que o cabeçalho (o ponteiro para o primeiro chunk a ser verificado) está sempre apontando para o chunk liberado mais recentemente desse tamanho. Então:
Quando um novo chunk é alocado desse tamanho, o cabeçalho está apontando para um chunk livre para usar. Como esse chunk livre está apontando para o próximo a ser usado, esse endereço é armazenado no cabeçalho para que a próxima alocação saiba onde obter um chunk disponível.
Quando um chunk é liberado, o chunk livre salvará o endereço para o chunk disponível atual e o endereço para esse novo chunk liberado será colocado no cabeçalho.
O tamanho máximo de uma lista encadeada é 0x80 e elas são organizadas de modo que um chunk de tamanho 0x20 estará no índice 0, um chunk de tamanho 0x30 estará no índice 1...
Chunks nos bins rápidos não são definidos como disponíveis, então eles são mantidos como chunks de bin rápido por algum tempo em vez de poderem ser mesclados com outros chunks livres ao redor deles.
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711/*FastbinsAn array of lists holding recently freed small chunks. Fastbinsare not doubly linked. It is faster to single-link them, andsince chunks are never removed from the middles of these lists,double linking is not necessary. Also, unlike regular bins, theyare not even processed in FIFO order (they use faster LIFO) sinceordering doesn't much matter in the transient contexts in whichfastbins are normally used.Chunks in fastbins keep their inuse bit set, so they cannotbe consolidated with other free chunks. malloc_consolidatereleases all chunks in fastbins and consolidates them withother free chunks.*/typedefstruct malloc_chunk *mfastbinptr;#definefastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[idx])/* offset 2 to use otherwise unindexable first 2 bins */#definefastbin_index(sz) \((((unsignedint) (sz)) >> (SIZE_SZ ==8?4:3)) -2)/* The maximum fastbin request size we support */#defineMAX_FAST_SIZE (80* SIZE_SZ /4)#defineNFASTBINS (fastbin_index (request2size (MAX_FAST_SIZE)) +1)
Adicionar um exemplo de chunk fastbin
```c #include #include
int main(void) { char *chunks[8]; int i;
// Loop to allocate memory 8 times for (i = 0; i < 8; i++) { chunks[i] = malloc(24); if (chunks[i] == NULL) { // Check if malloc failed fprintf(stderr, "Memory allocation failed at iteration %d\n", i); return 1; } printf("Address of chunk %d: %p\n", i, (void *)chunks[i]); }
// Loop to free the allocated memory for (i = 0; i < 8; i++) { free(chunks[i]); }
return 0; }
Observe como alocamos e liberamos 8 pedaços do mesmo tamanho para que preencham o tcache e o oitavo seja armazenado no fast chunk.
Compile e depure com um breakpoint no opcode `ret` da função `main`. Em seguida, com o `gef`, você pode ver que o bin tcache está cheio e um pedaço está no fast bin:
```bash
gef➤ heap bins
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
Tcachebins[idx=0, size=0x20, count=7] ← Chunk(addr=0xaaaaaaac1770, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1750, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1730, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1710, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac16f0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac16d0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac12a0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────────
Fastbins[idx=0, size=0x20] ← Chunk(addr=0xaaaaaaac1790, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
Fastbins[idx=1, size=0x30] 0x00
Bin não ordenado
O bin não ordenado é um cache usado pelo gerenciador de heap para tornar a alocação de memória mais rápida. Veja como funciona: Quando um programa libera um pedaço de memória e se esse pedaço não pode ser alocado em um tcache ou fast bin e não está colidindo com o chunk superior, o gerenciador de heap não o coloca imediatamente em um bin específico pequeno ou grande. Em vez disso, ele primeiro tenta fundir com quaisquer chunks livres vizinhos para criar um bloco maior de memória livre. Em seguida, ele coloca esse novo chunk em um bin geral chamado "bin não ordenado".
Quando um programa solicita memória, o gerenciador de heap verifica o bin não ordenado para ver se há um chunk de tamanho suficiente. Se encontrar, ele o utiliza imediatamente. Se não encontrar um chunk adequado no bin não ordenado, ele move todos os chunks nesta lista para seus bins correspondentes, seja pequeno ou grande, com base em seu tamanho.
Observe que se um chunk maior for dividido em 2 metades e o restante for maior que MINSIZE, ele será colocado de volta no bin não ordenado.
Portanto, o bin não ordenado é uma maneira de acelerar a alocação de memória reutilizando rapidamente a memória liberada recentemente e reduzindo a necessidade de pesquisas e fusões demoradas.
Observe que mesmo que os chunks sejam de categorias diferentes, se um chunk disponível estiver colidindo com outro chunk disponível (mesmo que originalmente pertençam a bins diferentes), eles serão fundidos.
Adicionar um exemplo de chunk não ordenado
```c #include #include
int main(void) { char *chunks[9]; int i;
// Loop to allocate memory 8 times for (i = 0; i < 9; i++) { chunks[i] = malloc(0x100); if (chunks[i] == NULL) { // Check if malloc failed fprintf(stderr, "Memory allocation failed at iteration %d\n", i); return 1; } printf("Address of chunk %d: %p\n", i, (void *)chunks[i]); }
// Loop to free the allocated memory for (i = 0; i < 8; i++) { free(chunks[i]); }
return 0; }
Observe como alocamos e liberamos 9 pedaços do mesmo tamanho para que **preencham o tcache** e o oitavo seja armazenado no unsorted bin porque é **muito grande para o fastbin** e o nono não é liberado, então o nono e o oitavo **não são mesclados com o top chunk**.
Compile e depure com um breakpoint no opcode `ret` da função `main`. Em seguida, com o `gef`, você pode ver que o bin tcache está cheio e um pedaço está no unsorted bin:
```bash
gef➤ heap bins
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
Tcachebins[idx=15, size=0x110, count=7] ← Chunk(addr=0xaaaaaaac1d10, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1c00, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1af0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac19e0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac18d0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac17c0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac12a0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────────
Fastbins[idx=0, size=0x20] 0x00
Fastbins[idx=1, size=0x30] 0x00
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] 0x00
Fastbins[idx=6, size=0x80] 0x00
─────────────────────────────────────────────────────────────────────── Unsorted Bin for arena at 0xfffff7f90b00 ───────────────────────────────────────────────────────────────────────
[+] unsorted_bins[0]: fw=0xaaaaaaac1e10, bk=0xaaaaaaac1e10
→ Chunk(addr=0xaaaaaaac1e20, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[+] Found 1 chunks in unsorted bin.
Bins Pequenos
Os bins pequenos são mais rápidos do que os bins grandes, mas mais lentos do que os bins rápidos.
Cada bin dos 62 terá pedaços do mesmo tamanho: 16, 24, ... (com um tamanho máximo de 504 bytes em 32 bits e 1024 em 64 bits). Isso ajuda na velocidade de encontrar o bin onde um espaço deve ser alocado e na inserção e remoção de entradas nessas listas.
Assim é calculado o tamanho do bin pequeno de acordo com o índice do bin:
Menor tamanho: 2*4*índice (por exemplo, índice 5 -> 40)
Maior tamanho: 2*8*índice (por exemplo, índice 5 -> 80)
// Loop to allocate memory 8 times for (i = 0; i < 9; i++) { chunks[i] = malloc(0x100); if (chunks[i] == NULL) { // Check if malloc failed fprintf(stderr, "Memory allocation failed at iteration %d\n", i); return 1; } printf("Address of chunk %d: %p\n", i, (void *)chunks[i]); }
// Loop to free the allocated memory for (i = 0; i < 8; i++) { free(chunks[i]); }
chunks[9] = malloc(0x110);
return 0; }
Observe como alocamos e liberamos 9 pedaços do mesmo tamanho para que **preencham o tcache** e o oitavo seja armazenado no unsorted bin porque é **muito grande para o fastbin** e o nono não é liberado, então o nono e o oitavo **não são mesclados com o top chunk**. Em seguida, alocamos um pedaço maior de 0x110, o que faz com que **o pedaço no unsorted bin vá para o small bin**.
Compile e depure com um breakpoint no opcode `ret` da função `main`. Em seguida, com o `gef`, você pode ver que o bin tcache está cheio e um pedaço está no small bin:
```bash
gef➤ heap bins
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
Tcachebins[idx=15, size=0x110, count=7] ← Chunk(addr=0xaaaaaaac1d10, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1c00, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1af0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac19e0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac18d0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac17c0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac12a0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────────
Fastbins[idx=0, size=0x20] 0x00
Fastbins[idx=1, size=0x30] 0x00
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] 0x00
Fastbins[idx=6, size=0x80] 0x00
─────────────────────────────────────────────────────────────────────── Unsorted Bin for arena at 0xfffff7f90b00 ───────────────────────────────────────────────────────────────────────
[+] Found 0 chunks in unsorted bin.
──────────────────────────────────────────────────────────────────────── Small Bins for arena at 0xfffff7f90b00 ────────────────────────────────────────────────────────────────────────
[+] small_bins[16]: fw=0xaaaaaaac1e10, bk=0xaaaaaaac1e10
→ Chunk(addr=0xaaaaaaac1e20, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[+] Found 1 chunks in 1 small non-empty bins.
Bins grandes
Ao contrário dos bins pequenos, que gerenciam pedaços de tamanhos fixos, cada bin grande lida com uma faixa de tamanhos de pedaços. Isso é mais flexível, permitindo que o sistema acomode vários tamanhos sem precisar de um bin separado para cada tamanho.
Em um alocador de memória, os bins grandes começam onde os bins pequenos terminam. As faixas para os bins grandes crescem progressivamente, o que significa que o primeiro bin pode abranger pedaços de 512 a 576 bytes, enquanto o próximo abrange de 576 a 640 bytes. Esse padrão continua, com o bin maior contendo todos os pedaços acima de 1MB.
Os bins grandes são mais lentos de operar em comparação com os bins pequenos porque eles precisam ordenar e pesquisar em uma lista de tamanhos de pedaços variados para encontrar o melhor encaixe para uma alocação. Quando um pedaço é inserido em um bin grande, ele precisa ser ordenado, e quando a memória é alocada, o sistema deve encontrar o pedaço certo. Esse trabalho extra os torna mais lentos, mas como alocações grandes são menos comuns do que as pequenas, é uma troca aceitável.
Existem:
32 bins de 64B de faixa (colidem com os bins pequenos)
16 bins de 512B de faixa (colidem com os bins pequenos)
8 bins de 4096B de faixa (parte colide com os bins pequenos)
4 bins de 32768B de faixa
2 bins de 262144B de faixa
1 bin para tamanhos restantes
Código dos tamanhos dos bins grandes
```c // From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711
</details>
<details>
<summary>Adicione um exemplo de grande bloco</summary>
```c
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunks[2];
chunks[0] = malloc(0x1500);
chunks[1] = malloc(0x1500);
free(chunks[0]);
chunks[0] = malloc(0x2000);
return 0;
}
Duas grandes alocações são realizadas, em seguida uma é liberada (colocando-a no bin não ordenado) e uma alocação maior é feita (movendo a liberada do bin não ordenado para o bin grande).
Compile e depure com um breakpoint no opcode ret da função main. Então com gef você pode ver que o bin tcache está cheio e um chunk está no bin grande:
gef➤heapbin──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
Alltcachebinsareempty───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────────
Fastbins[idx=0,size=0x20]0x00Fastbins[idx=1,size=0x30]0x00Fastbins[idx=2,size=0x40]0x00Fastbins[idx=3,size=0x50]0x00Fastbins[idx=4,size=0x60]0x00Fastbins[idx=5,size=0x70]0x00Fastbins[idx=6,size=0x80]0x00─────────────────────────────────────────────────────────────────────── Unsorted Bin for arena at 0xfffff7f90b00 ───────────────────────────────────────────────────────────────────────
[+] Found 0 chunks in unsorted bin.──────────────────────────────────────────────────────────────────────── Small Bins for arena at 0xfffff7f90b00 ────────────────────────────────────────────────────────────────────────
[+] Found 0 chunks in 0 small non-empty bins.──────────────────────────────────────────────────────────────────────── Large Bins for arena at 0xfffff7f90b00 ────────────────────────────────────────────────────────────────────────
[+] large_bins[100]: fw=0xaaaaaaac1290, bk=0xaaaaaaac1290→Chunk(addr=0xaaaaaaac12a0, size=0x1510, flags=PREV_INUSE|IS_MMAPPED|NON_MAIN_ARENA)[+] Found 1 chunks in 1 large non-empty bins.
Maior Fragmento
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711/*TopThe top-most available chunk (i.e., the one bordering the end ofavailable memory) is treated specially. It is never included inany bin, is used only if no other chunk is available, and isreleased back to the system if it is very large (seeM_TRIM_THRESHOLD). Because top initiallypoints to its own bin with initial zero size, thus forcingextension on the first malloc request, we avoid having any specialcode in malloc to check whether it even exists yet. But we stillneed to do so when getting memory from system, so we makeinitial_top treat the bin as a legal but unusable chunk during theinterval between initialization and the first call tosysmalloc. (This is somewhat delicate, since it relies onthe 2 preceding words to be zero during this interval as well.)*//* Conveniently, the unsorted bin can be used as dummy top on first call */#defineinitial_top(M) (unsorted_chunks (M))
Basicamente, este é um pedaço contendo toda a heap atualmente disponível. Quando um malloc é executado, se não houver nenhum pedaço livre disponível para usar, este pedaço superior reduzirá seu tamanho fornecendo o espaço necessário. O ponteiro para o Top Chunk é armazenado na estrutura malloc_state.
Além disso, no início, é possível usar o pedaço não ordenado como o pedaço superior.
Observe o exemplo do Top Chunk
```c #include #include
int main(void) { char *chunk; chunk = malloc(24); printf("Address of the chunk: %p\n", (void *)chunk); gets(chunk); return 0; }
Depois de compilar e depurar com um ponto de interrupção no opcode `ret` de `main`, vi que o malloc retornou o endereço `0xaaaaaaac12a0` e estes são os chunks:
```bash
gef➤ heap chunks
Chunk(addr=0xaaaaaaac1010, size=0x290, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[0x0000aaaaaaac1010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................]
Chunk(addr=0xaaaaaaac12a0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[0x0000aaaaaaac12a0 41 41 41 41 41 41 41 00 00 00 00 00 00 00 00 00 AAAAAAA.........]
Chunk(addr=0xaaaaaaac12c0, size=0x410, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[0x0000aaaaaaac12c0 41 64 64 72 65 73 73 20 6f 66 20 74 68 65 20 63 Address of the c]
Chunk(addr=0xaaaaaaac16d0, size=0x410, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[0x0000aaaaaaac16d0 41 41 41 41 41 41 41 0a 00 00 00 00 00 00 00 00 AAAAAAA.........]
Chunk(addr=0xaaaaaaac1ae0, size=0x20530, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← top chunk
Onde pode ser visto que o chunk superior está no endereço 0xaaaaaaac1ae0. Isso não é surpresa porque o último chunk alocado estava em 0xaaaaaaac12a0 com um tamanho de 0x410 e 0xaaaaaaac12a0 + 0x410 = 0xaaaaaaac1ae0.
Também é possível ver o tamanho do chunk superior no seu cabeçalho de chunk:
Quando o malloc é usado e um chunk é dividido (do unsorted bin ou do top chunk, por exemplo), o chunk criado a partir do restante do chunk dividido é chamado de Último Resto e seu ponteiro é armazenado na estrutura malloc_state.