Um die Effizienz zu verbessern, wie Chunks gespeichert werden, ist jeder Chunk nicht nur in einer verketteten Liste, sondern es gibt mehrere Typen. Dies sind die Bins und es gibt 5 Arten von Bins: 62 kleine Bins, 63 große Bins, 1 unsortierter Bin, 10 schnelle Bins und 64 Tcache-Bins pro Thread.
Die Anfangsadresse zu jedem unsortierten, kleinen und großen Bin befindet sich im selben Array. Der Index 0 ist ungenutzt, 1 ist der unsortierte Bin, Bins 2-64 sind kleine Bins und Bins 65-127 sind große Bins.
Tcache (Per-Thread Cache) Bins
Obwohl Threads versuchen, ihren eigenen Heap zu haben (siehe Arenas und Subheaps), besteht die Möglichkeit, dass ein Prozess mit vielen Threads (wie ein Webserver) den Heap mit anderen Threads teilen wird. In diesem Fall ist die Hauptlösung die Verwendung von Lockern, die die Threads erheblich verlangsamen können.
Wenn ein Thread einen Chunk freigibt, wenn er nicht zu groß ist, um im Tcache zuzugewiesen zu werden, und der entsprechende Tcache-Bin nicht voll ist (bereits 7 Chunks), wird er dort zugewiesen. Wenn er nicht in den Tcache gehen kann, muss er auf das Heap-Lock warten, um die Freigabeoperation global durchführen zu können.
Wenn ein Chunk zugewiesen wird, wird, wenn es einen freien Chunk der benötigten Größe im Tcache gibt, dieser verwendet, andernfalls muss er auf das Heap-Lock warten, um einen in den globalen Bins zu finden oder einen neuen zu erstellen.
Es gibt auch eine Optimierung, in diesem Fall wird der Thread seinen Tcache mit Heap-Chunks (7) der angeforderten Größe füllen, sodass er, falls er mehr benötigt, diese im Tcache findet.
Füge ein Beispiel für einen Tcache-Chunk hinzu
```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; }
Kompiliere es und debugge es mit einem Breakpoint im ret Opcode der main-Funktion. Dann kannst du mit gef den verwendeten tcache bin sehen:
```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)
Tcache-Strukturen & Funktionen
Im folgenden Code sind die max bins und chunks pro Index zu sehen, die tcache_entry-Struktur, die erstellt wurde, um doppelte Freigaben zu vermeiden, und tcache_perthread_struct, eine Struktur, die jeder Thread verwendet, um die Adressen zu jedem Index des Bins zu speichern.
tcache_entry und 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>
Die Funktion `__tcache_init` ist die Funktion, die den Speicher für das `tcache_perthread_struct` Objekt erstellt und zuweist.
<details>
<summary>tcache_init Code</summary>
```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));
}
}
Tcache-Indizes
Der Tcache hat mehrere Bins, abhängig von der Größe und den initialen Zeigern auf den ersten Chunk jedes Index und der Anzahl der Chunks pro Index, die sich innerhalb eines Chunks befinden. Das bedeutet, dass es möglich ist, alle Tcache-Startpunkte und die Anzahl der Tcache-Chunks zu finden, indem man den Chunk mit diesen Informationen (normalerweise den ersten) lokalisiert.
Schnelle Bins
Schnelle Bins sind darauf ausgelegt, die Speicherzuweisung für kleine Chunks zu beschleunigen, indem kürzlich freigegebene Chunks in einer Schnellzugriffsstruktur gehalten werden. Diese Bins verwenden einen Last-In, First-Out (LIFO)-Ansatz, was bedeutet, dass der zuletzt freigegebene Chunk der erste ist, der wiederverwendet wird, wenn eine neue Zuweisungsanforderung vorliegt. Dieses Verhalten ist vorteilhaft für die Geschwindigkeit, da es schneller ist, von oben eines Stapels (LIFO) zu entfernen und hinzuzufügen als von einer Warteschlange (FIFO).
Zusätzlich verwenden schnelle Bins einfach verkettete Listen, nicht doppelt verkettete, was die Geschwindigkeit weiter verbessert. Da Chunks in schnellen Bins nicht mit Nachbarn zusammengeführt werden, ist keine komplexe Struktur erforderlich, die eine Entfernung aus der Mitte ermöglicht. Eine einfach verkettete Liste ist einfacher und schneller für diese Operationen.
Im Grunde genommen passiert hier Folgendes: Der Header (der Zeiger auf den ersten Chunk, der überprüft werden soll) zeigt immer auf den zuletzt freigegebenen Chunk dieser Größe. Also:
Wenn ein neuer Chunk dieser Größe zugewiesen wird, zeigt der Header auf einen freien Chunk, der verwendet werden kann. Da dieser freie Chunk auf den nächsten Chunk zeigt, wird diese Adresse im Header gespeichert, damit die nächste Zuweisung weiß, wo ein verfügbarer Chunk zu finden ist.
Wenn ein Chunk freigegeben wird, speichert der freie Chunk die Adresse des aktuell verfügbaren Chunks, und die Adresse dieses neu freigegebenen Chunks wird im Header platziert.
Die maximale Größe einer verketteten Liste beträgt 0x80 und sie sind so organisiert, dass ein Chunk der Größe 0x20 im Index 0 und ein Chunk der Größe 0x30 im Index 1 wäre...
Chunks in schnellen Bins sind nicht als verfügbar gesetzt, sodass sie eine Zeit lang als schnelle Bin-Chunks behalten werden, anstatt mit anderen umgebenden freien Chunks zusammengeführt werden zu können.
// 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)
Fügen Sie ein Beispiel für einen Fastbin-Chunk hinzu
```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; }
Beachten Sie, wie wir 8 Blöcke derselben Größe zuweisen und freigeben, sodass sie den tcache füllen und der achte in den schnellen Block gespeichert wird.
Kompilieren Sie es und debuggen Sie es mit einem Haltepunkt im `ret` Opcode der `main` Funktion. Dann können Sie mit `gef` sehen, dass der tcache bin voll ist und ein Block im schnellen Bin ist:
```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
Unsortierte Bin
Die unsortierte Bin ist ein Cache, der vom Heap-Manager verwendet wird, um die Speicherzuweisung schneller zu machen. So funktioniert es: Wenn ein Programm einen Block freigibt und dieser Block nicht in einem tcache oder schnellen Bin zugewiesen werden kann und nicht mit dem Top-Chunk kollidiert, platziert der Heap-Manager ihn nicht sofort in einer bestimmten kleinen oder großen Bin. Stattdessen versucht er zuerst, ihn mit benachbarten freien Blöcken zu fusionieren, um einen größeren Block freien Speichers zu schaffen. Dann wird dieser neue Block in eine allgemeine Bin namens "unsortierte Bin" gelegt.
Wenn ein Programm Speicher anfordert, prüft der Heap-Manager die unsortierte Bin, um zu sehen, ob ein Block ausreichender Größe vorhanden ist. Wenn er einen findet, wird er ihn sofort verwenden. Wenn er keinen geeigneten Block in der unsortierten Bin findet, verschiebt er alle Blöcke in dieser Liste in ihre entsprechenden Bins, entweder klein oder groß, basierend auf ihrer Größe.
Beachten Sie, dass, wenn ein größerer Block in 2 Hälften geteilt wird und der Rest größer als MINSIZE ist, er wieder in die unsortierte Bin gelegt wird.
Die unsortierte Bin ist also eine Möglichkeit, die Speicherzuweisung zu beschleunigen, indem kürzlich freigegebener Speicher schnell wiederverwendet wird und die Notwendigkeit zeitaufwändiger Suchen und Fusionen verringert wird.
Beachten Sie, dass selbst wenn Blöcke aus verschiedenen Kategorien stammen, wenn ein verfügbarer Block mit einem anderen verfügbaren Block kollidiert (auch wenn sie ursprünglich zu verschiedenen Bins gehörten), sie zusammengeführt werden.
Fügen Sie ein Beispiel für einen unsortierten Block hinzu
```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; }
Beachten Sie, wie wir 9 Chunks derselben Größe zuweisen und freigeben, sodass sie **den tcache füllen** und der achte in der unsortierten Bin gespeichert wird, weil er **zu groß für den fastbin ist** und der neunte nicht freigegeben wird, sodass der neunte und der achte **nicht mit dem oberen Chunk zusammengeführt werden**.
Kompilieren Sie es und debuggen Sie es mit einem Haltepunkt im `ret` Opcode der `main` Funktion. Dann können Sie mit `gef` sehen, dass der tcache Bin voll ist und ein Chunk in der unsortierten Bin ist:
```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.
Kleine Bins
Kleine Bins sind schneller als große Bins, aber langsamer als schnelle Bins.
Jeder Bin der 62 wird Chunks der gleichen Größe haben: 16, 24, ... (mit einer maximalen Größe von 504 Bytes in 32-Bit und 1024 in 64-Bit). Dies hilft bei der Geschwindigkeit, den Bin zu finden, in dem ein Platz zugewiesen werden soll, sowie beim Einfügen und Entfernen von Einträgen in diesen Listen.
So wird die Größe des kleinen Bins entsprechend dem Index des Bins berechnet:
// 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; }
Beachten Sie, wie wir 9 Chunks derselben Größe zuweisen und freigeben, sodass sie **den tcache füllen** und der achte in der unsortierten Bin gespeichert wird, weil er **zu groß für den fastbin ist** und der neunte nicht freigegeben wird, sodass der neunte und der achte **nicht mit dem Top-Chunk zusammengeführt werden**. Dann weisen wir einen größeren Chunk von 0x110 zu, was dazu führt, dass **der Chunk in der unsortierten Bin in die kleine Bin geht**.
Kompilieren Sie es und debuggen Sie es mit einem Breakpoint im `ret` Opcode der `main` Funktion. Dann können Sie mit `gef` sehen, dass die tcache Bin voll ist und ein Chunk in der kleinen Bin ist:
```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.
Große Bins
Im Gegensatz zu kleinen Bins, die Chunks fester Größen verwalten, verwaltet jeder große Bin einen Bereich von Chunk-Größen. Dies ist flexibler und ermöglicht es dem System, verschiedene Größen unterzubringen, ohne für jede Größe einen separaten Bin zu benötigen.
In einem Speicher-Allocator beginnen große Bins dort, wo kleine Bins enden. Die Bereiche für große Bins wachsen progressiv größer, was bedeutet, dass der erste Bin Chunks von 512 bis 576 Bytes abdecken könnte, während der nächste von 576 bis 640 Bytes abdeckt. Dieses Muster setzt sich fort, wobei der größte Bin alle Chunks über 1 MB enthält.
Große Bins sind langsamer im Betrieb im Vergleich zu kleinen Bins, da sie eine Liste von variierenden Chunk-Größen sortieren und durchsuchen müssen, um die beste Passform für eine Zuweisung zu finden. Wenn ein Chunk in einen großen Bin eingefügt wird, muss er sortiert werden, und wenn Speicher zugewiesen wird, muss das System den richtigen Chunk finden. Diese zusätzliche Arbeit macht sie langsamer, aber da große Zuweisungen seltener sind als kleine, ist es ein akzeptabler Kompromiss.
Es gibt:
32 Bins im Bereich von 64B (kollidieren mit kleinen Bins)
16 Bins im Bereich von 512B (kollidieren mit kleinen Bins)
8 Bins im Bereich von 4096B (teilweise kollidieren mit kleinen Bins)
4 Bins im Bereich von 32768B
2 Bins im Bereich von 262144B
1 Bin für verbleibende Größen
Code für große Bin-Größen
```c // From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711
</details>
<details>
<summary>Fügen Sie ein großes Beispiel hinzu</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;
}
2 große Zuweisungen werden durchgeführt, dann wird eine freigegeben (was sie in den unsortierten Bin bringt) und eine größere Zuweisung wird vorgenommen (wodurch die freigegebene von dem unsortierten Bin in den großen Bin verschoben wird).
Kompiliere es und debugge es mit einem Haltepunkt im ret Opcode der main Funktion. Dann kannst du mit gef sehen, dass der tcache Bin voll ist und ein Chunk im großen Bin ist:
gef➤heapbin────────────────────────────────────────────────────────────────────────────────Tcachebinsforthread1────────────────────────────────────────────────────────────────────────────────Alltcachebinsareempty─────────────────────────────────────────────────────────────────────────Fastbinsforarenaat0xfffff7f90b00─────────────────────────────────────────────────────────────────────────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───────────────────────────────────────────────────────────────────────UnsortedBinforarenaat0xfffff7f90b00───────────────────────────────────────────────────────────────────────[+] Found 0 chunks in unsorted bin.────────────────────────────────────────────────────────────────────────SmallBinsforarenaat0xfffff7f90b00────────────────────────────────────────────────────────────────────────[+] Found 0 chunks in 0 small non-empty bins.────────────────────────────────────────────────────────────────────────LargeBinsforarenaat0xfffff7f90b00────────────────────────────────────────────────────────────────────────[+] 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.
Top Chunk
// 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))
Grundsätzlich handelt es sich hierbei um einen Block, der den gesamten derzeit verfügbaren Heap enthält. Wenn ein malloc durchgeführt wird und kein verfügbarer freier Block vorhanden ist, wird dieser Top Chunk seine Größe reduzieren, um den notwendigen Platz zu schaffen.
Der Zeiger auf den Top Chunk wird in der malloc_state-Struktur gespeichert.
Darüber hinaus ist es zu Beginn möglich, den unsortierten Block als Top Chunk zu verwenden.
Beobachten Sie das Top Chunk-Beispiel
```c #include #include
int main(void) { char *chunk; chunk = malloc(24); printf("Address of the chunk: %p\n", (void *)chunk); gets(chunk); return 0; }
Nach dem Kompilieren und Debuggen mit einem Haltepunkt im `ret`-Opcode von `main` sah ich, dass malloc die Adresse `0xaaaaaaac12a0` zurückgab und dies sind die 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
Wo zu sehen ist, dass der Top-Chuck sich an der Adresse 0xaaaaaaac1ae0 befindet. Das ist keine Überraschung, da der zuletzt zugewiesene Chunk sich in 0xaaaaaaac12a0 mit einer Größe von 0x410 befand und 0xaaaaaaac12a0 + 0x410 = 0xaaaaaaac1ae0 .
Es ist auch möglich, die Länge des Top-Chunks in seinem Chunk-Header zu sehen:
Wenn malloc verwendet wird und ein Chunk geteilt wird (zum Beispiel aus dem unsortierten Bin oder vom Top-Chunk), wird der Chunk, der aus dem Rest des geteilten Chunks erstellt wird, als Letzte Rest bezeichnet und sein Zeiger wird in der malloc_state-Struktur gespeichert.