mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
1699 lines
57 KiB
Markdown
1699 lines
57 KiB
Markdown
# malloc & sysmalloc
|
||
|
||
{{#include ../../../banners/hacktricks-training.md}}
|
||
|
||
## Підсумок порядку виділення <a href="#libc_malloc" id="libc_malloc"></a>
|
||
|
||
(У цьому підсумку не пояснюються перевірки, і деякі випадки були опущені для стислості)
|
||
|
||
1. `__libc_malloc` намагається отримати шматок з tcache, якщо ні, викликає `_int_malloc`
|
||
2. `_int_malloc` :
|
||
1. Намагається створити арену, якщо її немає
|
||
2. Якщо є шматок з швидкого біну правильного розміру, використовуйте його
|
||
1. Заповніть tcache іншими швидкими шматками
|
||
3. Якщо є шматок з малого біну правильного розміру, використовуйте його
|
||
1. Заповніть tcache іншими шматками цього розміру
|
||
4. Якщо запитуваний розмір не для малих бінів, об'єднайте швидкий бін в неупорядкований бін
|
||
5. Перевірте неупорядкований бін, використовуйте перший шматок з достатнім місцем
|
||
1. Якщо знайдений шматок більший, розділіть його, щоб повернути частину, і додайте залишок назад до неупорядкованого біна
|
||
2. Якщо шматок того ж розміру, що й запитуваний, використовуйте його для заповнення tcache замість повернення (поки tcache не заповниться, потім поверніть наступний)
|
||
3. Для кожного перевіреного шматка меншого розміру покладіть його в його відповідний малий або великий бін
|
||
6. Перевірте великий бін за індексом запитуваного розміру
|
||
1. Почніть шукати з першого шматка, який більший за запитуваний розмір, якщо знайдеться, поверніть його і додайте залишки до малого біна
|
||
7. Перевірте великі біни з наступних індексів до кінця
|
||
1. З наступного більшого індексу перевірте наявність будь-якого шматка, розділіть перший знайдений шматок, щоб використовувати його для запитуваного розміру, і додайте залишок до неупорядкованого біна
|
||
8. Якщо нічого не знайдено в попередніх бінах, отримайте шматок з верхнього шматка
|
||
9. Якщо верхній шматок не був достатньо великим, збільшіть його за допомогою `sysmalloc`
|
||
|
||
## \_\_libc_malloc <a href="#libc_malloc" id="libc_malloc"></a>
|
||
|
||
Функція `malloc` насправді викликає `__libc_malloc`. Ця функція перевірить tcache, щоб дізнатися, чи є доступний шматок бажаного розміру. Якщо є, вона його використає, а якщо ні, перевірить, чи це однопотокова програма, і в цьому випадку викличе `_int_malloc` в основній арені, а якщо ні, викличе `_int_malloc` в арені потоку.
|
||
|
||
<details>
|
||
|
||
<summary>__libc_malloc код</summary>
|
||
```c
|
||
// From https://github.com/bminor/glibc/blob/master/malloc/malloc.c
|
||
|
||
#if IS_IN (libc)
|
||
void *
|
||
__libc_malloc (size_t bytes)
|
||
{
|
||
mstate ar_ptr;
|
||
void *victim;
|
||
|
||
_Static_assert (PTRDIFF_MAX <= SIZE_MAX / 2,
|
||
"PTRDIFF_MAX is not more than half of SIZE_MAX");
|
||
|
||
if (!__malloc_initialized)
|
||
ptmalloc_init ();
|
||
#if USE_TCACHE
|
||
/* int_free also calls request2size, be careful to not pad twice. */
|
||
size_t tbytes = checked_request2size (bytes);
|
||
if (tbytes == 0)
|
||
{
|
||
__set_errno (ENOMEM);
|
||
return NULL;
|
||
}
|
||
size_t tc_idx = csize2tidx (tbytes);
|
||
|
||
MAYBE_INIT_TCACHE ();
|
||
|
||
DIAG_PUSH_NEEDS_COMMENT;
|
||
if (tc_idx < mp_.tcache_bins
|
||
&& tcache != NULL
|
||
&& tcache->counts[tc_idx] > 0)
|
||
{
|
||
victim = tcache_get (tc_idx);
|
||
return tag_new_usable (victim);
|
||
}
|
||
DIAG_POP_NEEDS_COMMENT;
|
||
#endif
|
||
|
||
if (SINGLE_THREAD_P)
|
||
{
|
||
victim = tag_new_usable (_int_malloc (&main_arena, bytes));
|
||
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
|
||
&main_arena == arena_for_chunk (mem2chunk (victim)));
|
||
return victim;
|
||
}
|
||
|
||
arena_get (ar_ptr, bytes);
|
||
|
||
victim = _int_malloc (ar_ptr, bytes);
|
||
/* Retry with another arena only if we were able to find a usable arena
|
||
before. */
|
||
if (!victim && ar_ptr != NULL)
|
||
{
|
||
LIBC_PROBE (memory_malloc_retry, 1, bytes);
|
||
ar_ptr = arena_get_retry (ar_ptr, bytes);
|
||
victim = _int_malloc (ar_ptr, bytes);
|
||
}
|
||
|
||
if (ar_ptr != NULL)
|
||
__libc_lock_unlock (ar_ptr->mutex);
|
||
|
||
victim = tag_new_usable (victim);
|
||
|
||
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
|
||
ar_ptr == arena_for_chunk (mem2chunk (victim)));
|
||
return victim;
|
||
}
|
||
```
|
||
</details>
|
||
|
||
Зверніть увагу, що він завжди позначатиме повернений вказівник `tag_new_usable`, з коду:
|
||
```c
|
||
void *tag_new_usable (void *ptr)
|
||
|
||
Allocate a new random color and use it to color the user region of
|
||
a chunk; this may include data from the subsequent chunk's header
|
||
if tagging is sufficiently fine grained. Returns PTR suitably
|
||
recolored for accessing the memory there.
|
||
```
|
||
## \_int_malloc <a href="#int_malloc" id="int_malloc"></a>
|
||
|
||
Це функція, яка виділяє пам'ять, використовуючи інші контейнери та верхній шматок.
|
||
|
||
- Початок
|
||
|
||
Вона починає з визначення деяких змінних і отримання реального розміру, який потрібно для запитуваного простору пам'яті:
|
||
|
||
<details>
|
||
|
||
<summary>_int_malloc початок</summary>
|
||
```c
|
||
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L3847
|
||
static void *
|
||
_int_malloc (mstate av, size_t bytes)
|
||
{
|
||
INTERNAL_SIZE_T nb; /* normalized request size */
|
||
unsigned int idx; /* associated bin index */
|
||
mbinptr bin; /* associated bin */
|
||
|
||
mchunkptr victim; /* inspected/selected chunk */
|
||
INTERNAL_SIZE_T size; /* its size */
|
||
int victim_index; /* its bin index */
|
||
|
||
mchunkptr remainder; /* remainder from a split */
|
||
unsigned long remainder_size; /* its size */
|
||
|
||
unsigned int block; /* bit map traverser */
|
||
unsigned int bit; /* bit map traverser */
|
||
unsigned int map; /* current word of binmap */
|
||
|
||
mchunkptr fwd; /* misc temp for linking */
|
||
mchunkptr bck; /* misc temp for linking */
|
||
|
||
#if USE_TCACHE
|
||
size_t tcache_unsorted_count; /* count of unsorted chunks processed */
|
||
#endif
|
||
|
||
/*
|
||
Convert request size to internal form by adding SIZE_SZ bytes
|
||
overhead plus possibly more to obtain necessary alignment and/or
|
||
to obtain a size of at least MINSIZE, the smallest allocatable
|
||
size. Also, checked_request2size returns false for request sizes
|
||
that are so large that they wrap around zero when padded and
|
||
aligned.
|
||
*/
|
||
|
||
nb = checked_request2size (bytes);
|
||
if (nb == 0)
|
||
{
|
||
__set_errno (ENOMEM);
|
||
return NULL;
|
||
}
|
||
```
|
||
</details>
|
||
|
||
### Арена
|
||
|
||
У малоймовірному випадку, якщо немає придатних арен, використовується `sysmalloc` для отримання блоку з `mmap`:
|
||
|
||
<details>
|
||
|
||
<summary>_int_malloc не арена</summary>
|
||
```c
|
||
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L3885C3-L3893C6
|
||
/* There are no usable arenas. Fall back to sysmalloc to get a chunk from
|
||
mmap. */
|
||
if (__glibc_unlikely (av == NULL))
|
||
{
|
||
void *p = sysmalloc (nb, av);
|
||
if (p != NULL)
|
||
alloc_perturb (p, bytes);
|
||
return p;
|
||
}
|
||
```
|
||
</details>
|
||
|
||
### Fast Bin
|
||
|
||
Якщо потрібний розмір знаходиться в межах розмірів Fast Bins, спробуйте використати шматок з швидкого бін. В основному, на основі розміру, він знайде індекс швидкого біну, де повинні бути розташовані дійсні шматки, і, якщо такі є, поверне один з них.\
|
||
Більше того, якщо tcache увімкнено, він **заповнить tcache бін цього розміру швидкими бінами**.
|
||
|
||
Під час виконання цих дій виконуються деякі перевірки безпеки:
|
||
|
||
- Якщо шматок неправильно вирівняний: `malloc(): unaligned fastbin chunk detected 2`
|
||
- Якщо наступний шматок неправильно вирівняний: `malloc(): unaligned fastbin chunk detected`
|
||
- Якщо повернутий шматок має неправильний розмір через свій індекс у швидкому біні: `malloc(): memory corruption (fast)`
|
||
- Якщо будь-який шматок, використаний для заповнення tcache, неправильно вирівняний: `malloc(): unaligned fastbin chunk detected 3`
|
||
|
||
<details>
|
||
|
||
<summary>_int_malloc fast bin</summary>
|
||
```c
|
||
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L3895C3-L3967C6
|
||
/*
|
||
If the size qualifies as a fastbin, first check corresponding bin.
|
||
This code is safe to execute even if av is not yet initialized, so we
|
||
can try it without checking, which saves some time on this fast path.
|
||
*/
|
||
|
||
#define REMOVE_FB(fb, victim, pp) \
|
||
do \
|
||
{ \
|
||
victim = pp; \
|
||
if (victim == NULL) \
|
||
break; \
|
||
pp = REVEAL_PTR (victim->fd); \
|
||
if (__glibc_unlikely (pp != NULL && misaligned_chunk (pp))) \
|
||
malloc_printerr ("malloc(): unaligned fastbin chunk detected"); \
|
||
} \
|
||
while ((pp = catomic_compare_and_exchange_val_acq (fb, pp, victim)) \
|
||
!= victim); \
|
||
|
||
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
|
||
{
|
||
idx = fastbin_index (nb);
|
||
mfastbinptr *fb = &fastbin (av, idx);
|
||
mchunkptr pp;
|
||
victim = *fb;
|
||
|
||
if (victim != NULL)
|
||
{
|
||
if (__glibc_unlikely (misaligned_chunk (victim)))
|
||
malloc_printerr ("malloc(): unaligned fastbin chunk detected 2");
|
||
|
||
if (SINGLE_THREAD_P)
|
||
*fb = REVEAL_PTR (victim->fd);
|
||
else
|
||
REMOVE_FB (fb, pp, victim);
|
||
if (__glibc_likely (victim != NULL))
|
||
{
|
||
size_t victim_idx = fastbin_index (chunksize (victim));
|
||
if (__builtin_expect (victim_idx != idx, 0))
|
||
malloc_printerr ("malloc(): memory corruption (fast)");
|
||
check_remalloced_chunk (av, victim, nb);
|
||
#if USE_TCACHE
|
||
/* While we're here, if we see other chunks of the same size,
|
||
stash them in the tcache. */
|
||
size_t tc_idx = csize2tidx (nb);
|
||
if (tcache != NULL && tc_idx < mp_.tcache_bins)
|
||
{
|
||
mchunkptr tc_victim;
|
||
|
||
/* While bin not empty and tcache not full, copy chunks. */
|
||
while (tcache->counts[tc_idx] < mp_.tcache_count
|
||
&& (tc_victim = *fb) != NULL)
|
||
{
|
||
if (__glibc_unlikely (misaligned_chunk (tc_victim)))
|
||
malloc_printerr ("malloc(): unaligned fastbin chunk detected 3");
|
||
if (SINGLE_THREAD_P)
|
||
*fb = REVEAL_PTR (tc_victim->fd);
|
||
else
|
||
{
|
||
REMOVE_FB (fb, pp, tc_victim);
|
||
if (__glibc_unlikely (tc_victim == NULL))
|
||
break;
|
||
}
|
||
tcache_put (tc_victim, tc_idx);
|
||
}
|
||
}
|
||
#endif
|
||
void *p = chunk2mem (victim);
|
||
alloc_perturb (p, bytes);
|
||
return p;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
</details>
|
||
|
||
### Малий Бін
|
||
|
||
Як зазначено в коментарі, малі бінари містять один розмір на індекс, тому перевірка наявності дійсного шматка є дуже швидкою, тому після швидких бінів перевіряються малі біні.
|
||
|
||
Перша перевірка полягає в тому, щоб з'ясувати, чи може запитуваний розмір бути всередині малого біна. У цьому випадку отримайте відповідний **індекс** всередині малого біна і перевірте, чи є **будь-який доступний шматок**.
|
||
|
||
Потім виконується перевірка безпеки:
|
||
|
||
- якщо `victim->bk->fd = victim`. Щоб перевірити, що обидва шматки правильно пов'язані.
|
||
|
||
У цьому випадку шматок **отримує біт `inuse`,** подвоєний зв'язний список виправляється, тому цей шматок зникає з нього (оскільки він буде використаний), і біт не основної арени встановлюється за потреби.
|
||
|
||
Нарешті, **заповніть індекс tcache запитуваного розміру** іншими шматками всередині малого біна (якщо такі є).
|
||
|
||
<details>
|
||
|
||
<summary>_int_malloc малий бін</summary>
|
||
```c
|
||
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L3895C3-L3967C6
|
||
|
||
/*
|
||
If a small request, check regular bin. Since these "smallbins"
|
||
hold one size each, no searching within bins is necessary.
|
||
(For a large request, we need to wait until unsorted chunks are
|
||
processed to find best fit. But for small ones, fits are exact
|
||
anyway, so we can check now, which is faster.)
|
||
*/
|
||
|
||
if (in_smallbin_range (nb))
|
||
{
|
||
idx = smallbin_index (nb);
|
||
bin = bin_at (av, idx);
|
||
|
||
if ((victim = last (bin)) != bin)
|
||
{
|
||
bck = victim->bk;
|
||
if (__glibc_unlikely (bck->fd != victim))
|
||
malloc_printerr ("malloc(): smallbin double linked list corrupted");
|
||
set_inuse_bit_at_offset (victim, nb);
|
||
bin->bk = bck;
|
||
bck->fd = bin;
|
||
|
||
if (av != &main_arena)
|
||
set_non_main_arena (victim);
|
||
check_malloced_chunk (av, victim, nb);
|
||
#if USE_TCACHE
|
||
/* While we're here, if we see other chunks of the same size,
|
||
stash them in the tcache. */
|
||
size_t tc_idx = csize2tidx (nb);
|
||
if (tcache != NULL && tc_idx < mp_.tcache_bins)
|
||
{
|
||
mchunkptr tc_victim;
|
||
|
||
/* While bin not empty and tcache not full, copy chunks over. */
|
||
while (tcache->counts[tc_idx] < mp_.tcache_count
|
||
&& (tc_victim = last (bin)) != bin)
|
||
{
|
||
if (tc_victim != 0)
|
||
{
|
||
bck = tc_victim->bk;
|
||
set_inuse_bit_at_offset (tc_victim, nb);
|
||
if (av != &main_arena)
|
||
set_non_main_arena (tc_victim);
|
||
bin->bk = bck;
|
||
bck->fd = bin;
|
||
|
||
tcache_put (tc_victim, tc_idx);
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
void *p = chunk2mem (victim);
|
||
alloc_perturb (p, bytes);
|
||
return p;
|
||
}
|
||
}
|
||
```
|
||
</details>
|
||
|
||
### malloc_consolidate
|
||
|
||
Якщо це не маленький шматок, це великий шматок, і в цьому випадку **`malloc_consolidate`** викликається, щоб уникнути фрагментації пам'яті.
|
||
|
||
<details>
|
||
|
||
<summary>виклик malloc_consolidate</summary>
|
||
```c
|
||
/*
|
||
If this is a large request, consolidate fastbins before continuing.
|
||
While it might look excessive to kill all fastbins before
|
||
even seeing if there is space available, this avoids
|
||
fragmentation problems normally associated with fastbins.
|
||
Also, in practice, programs tend to have runs of either small or
|
||
large requests, but less often mixtures, so consolidation is not
|
||
invoked all that often in most programs. And the programs that
|
||
it is called frequently in otherwise tend to fragment.
|
||
*/
|
||
|
||
else
|
||
{
|
||
idx = largebin_index (nb);
|
||
if (atomic_load_relaxed (&av->have_fastchunks))
|
||
malloc_consolidate (av);
|
||
}
|
||
|
||
```
|
||
</details>
|
||
|
||
Функція malloc consolidate в основному видаляє шматки з швидкого бін і поміщає їх у несортований бін. Після наступного malloc ці шматки будуть організовані у свої відповідні маленькі/швидкі біни.
|
||
|
||
Зверніть увагу, що якщо під час видалення цих шматків вони виявляються з попередніми або наступними шматками, які не використовуються, вони будуть **непов'язані та об'єднані** перед тим, як помістити фінальний шматок у **несортований** бін.
|
||
|
||
Для кожного шматка швидкого біна виконується кілька перевірок безпеки:
|
||
|
||
- Якщо шматок не вирівняний, спрацьовує: `malloc_consolidate(): unaligned fastbin chunk detected`
|
||
- Якщо шматок має інший розмір, ніж той, який повинен бути через індекс, в якому він знаходиться: `malloc_consolidate(): invalid chunk size`
|
||
- Якщо попередній шматок не використовується, а попередній шматок має розмір, відмінний від вказаного `prev_chunk`: `corrupted size vs. prev_size in fastbins`
|
||
|
||
<details>
|
||
|
||
<summary>функція malloc_consolidate</summary>
|
||
```c
|
||
// https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L4810C1-L4905C2
|
||
|
||
static void malloc_consolidate(mstate av)
|
||
{
|
||
mfastbinptr* fb; /* current fastbin being consolidated */
|
||
mfastbinptr* maxfb; /* last fastbin (for loop control) */
|
||
mchunkptr p; /* current chunk being consolidated */
|
||
mchunkptr nextp; /* next chunk to consolidate */
|
||
mchunkptr unsorted_bin; /* bin header */
|
||
mchunkptr first_unsorted; /* chunk to link to */
|
||
|
||
/* These have same use as in free() */
|
||
mchunkptr nextchunk;
|
||
INTERNAL_SIZE_T size;
|
||
INTERNAL_SIZE_T nextsize;
|
||
INTERNAL_SIZE_T prevsize;
|
||
int nextinuse;
|
||
|
||
atomic_store_relaxed (&av->have_fastchunks, false);
|
||
|
||
unsorted_bin = unsorted_chunks(av);
|
||
|
||
/*
|
||
Remove each chunk from fast bin and consolidate it, placing it
|
||
then in unsorted bin. Among other reasons for doing this,
|
||
placing in unsorted bin avoids needing to calculate actual bins
|
||
until malloc is sure that chunks aren't immediately going to be
|
||
reused anyway.
|
||
*/
|
||
|
||
maxfb = &fastbin (av, NFASTBINS - 1);
|
||
fb = &fastbin (av, 0);
|
||
do {
|
||
p = atomic_exchange_acquire (fb, NULL);
|
||
if (p != 0) {
|
||
do {
|
||
{
|
||
if (__glibc_unlikely (misaligned_chunk (p)))
|
||
malloc_printerr ("malloc_consolidate(): "
|
||
"unaligned fastbin chunk detected");
|
||
|
||
unsigned int idx = fastbin_index (chunksize (p));
|
||
if ((&fastbin (av, idx)) != fb)
|
||
malloc_printerr ("malloc_consolidate(): invalid chunk size");
|
||
}
|
||
|
||
check_inuse_chunk(av, p);
|
||
nextp = REVEAL_PTR (p->fd);
|
||
|
||
/* Slightly streamlined version of consolidation code in free() */
|
||
size = chunksize (p);
|
||
nextchunk = chunk_at_offset(p, size);
|
||
nextsize = chunksize(nextchunk);
|
||
|
||
if (!prev_inuse(p)) {
|
||
prevsize = prev_size (p);
|
||
size += prevsize;
|
||
p = chunk_at_offset(p, -((long) prevsize));
|
||
if (__glibc_unlikely (chunksize(p) != prevsize))
|
||
malloc_printerr ("corrupted size vs. prev_size in fastbins");
|
||
unlink_chunk (av, p);
|
||
}
|
||
|
||
if (nextchunk != av->top) {
|
||
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
|
||
|
||
if (!nextinuse) {
|
||
size += nextsize;
|
||
unlink_chunk (av, nextchunk);
|
||
} else
|
||
clear_inuse_bit_at_offset(nextchunk, 0);
|
||
|
||
first_unsorted = unsorted_bin->fd;
|
||
unsorted_bin->fd = p;
|
||
first_unsorted->bk = p;
|
||
|
||
if (!in_smallbin_range (size)) {
|
||
p->fd_nextsize = NULL;
|
||
p->bk_nextsize = NULL;
|
||
}
|
||
|
||
set_head(p, size | PREV_INUSE);
|
||
p->bk = unsorted_bin;
|
||
p->fd = first_unsorted;
|
||
set_foot(p, size);
|
||
}
|
||
|
||
else {
|
||
size += nextsize;
|
||
set_head(p, size | PREV_INUSE);
|
||
av->top = p;
|
||
}
|
||
|
||
} while ( (p = nextp) != 0);
|
||
|
||
}
|
||
} while (fb++ != maxfb);
|
||
}
|
||
```
|
||
</details>
|
||
|
||
### Невпорядкований бін
|
||
|
||
Час перевірити невпорядкований бін на наявність потенційно дійсного блоку для використання.
|
||
|
||
#### Початок
|
||
|
||
Це починається з великого циклу for, який буде проходити через невпорядкований бін у напрямку `bk`, поки не досягне кінця (структура арени) з `while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))`
|
||
|
||
Крім того, деякі перевірки безпеки виконуються щоразу, коли розглядається новий блок:
|
||
|
||
- Якщо розмір блоку дивний (занадто малий або занадто великий): `malloc(): invalid size (unsorted)`
|
||
- Якщо розмір наступного блоку дивний (занадто малий або занадто великий): `malloc(): invalid next size (unsorted)`
|
||
- Якщо попередній розмір, вказаний наступним блоком, відрізняється від розміру блоку: `malloc(): mismatching next->prev_size (unsorted)`
|
||
- Якщо не `victim->bck->fd == victim` або не `victim->fd == av` (арена): `malloc(): unsorted double linked list corrupted`
|
||
- Оскільки ми завжди перевіряємо останній, його `fd` завжди має вказувати на структуру арени.
|
||
- Якщо наступний блок не вказує, що попередній використовується: `malloc(): invalid next->prev_inuse (unsorted)`
|
||
|
||
<details>
|
||
|
||
<summary><code>_int_malloc</code> початок невпорядкованого біна</summary>
|
||
```c
|
||
/*
|
||
Process recently freed or remaindered chunks, taking one only if
|
||
it is exact fit, or, if this a small request, the chunk is remainder from
|
||
the most recent non-exact fit. Place other traversed chunks in
|
||
bins. Note that this step is the only place in any routine where
|
||
chunks are placed in bins.
|
||
|
||
The outer loop here is needed because we might not realize until
|
||
near the end of malloc that we should have consolidated, so must
|
||
do so and retry. This happens at most once, and only when we would
|
||
otherwise need to expand memory to service a "small" request.
|
||
*/
|
||
|
||
#if USE_TCACHE
|
||
INTERNAL_SIZE_T tcache_nb = 0;
|
||
size_t tc_idx = csize2tidx (nb);
|
||
if (tcache != NULL && tc_idx < mp_.tcache_bins)
|
||
tcache_nb = nb;
|
||
int return_cached = 0;
|
||
|
||
tcache_unsorted_count = 0;
|
||
#endif
|
||
|
||
for (;; )
|
||
{
|
||
int iters = 0;
|
||
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
|
||
{
|
||
bck = victim->bk;
|
||
size = chunksize (victim);
|
||
mchunkptr next = chunk_at_offset (victim, size);
|
||
|
||
if (__glibc_unlikely (size <= CHUNK_HDR_SZ)
|
||
|| __glibc_unlikely (size > av->system_mem))
|
||
malloc_printerr ("malloc(): invalid size (unsorted)");
|
||
if (__glibc_unlikely (chunksize_nomask (next) < CHUNK_HDR_SZ)
|
||
|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
|
||
malloc_printerr ("malloc(): invalid next size (unsorted)");
|
||
if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))
|
||
malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
|
||
if (__glibc_unlikely (bck->fd != victim)
|
||
|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))
|
||
malloc_printerr ("malloc(): unsorted double linked list corrupted");
|
||
if (__glibc_unlikely (prev_inuse (next)))
|
||
malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");
|
||
|
||
```
|
||
</details>
|
||
|
||
#### якщо `in_smallbin_range`
|
||
|
||
Якщо шматок більший за запитуваний розмір, використовуйте його, і помістіть решту простору шматка в неупорядкований список та оновіть `last_remainder` з ним.
|
||
|
||
<details>
|
||
|
||
<summary><code>_int_malloc</code> неупорядкований бін <code>in_smallbin_range</code></summary>
|
||
```c
|
||
// From https://github.com/bminor/glibc/blob/master/malloc/malloc.c#L4090C11-L4124C14
|
||
|
||
/*
|
||
If a small request, try to use last remainder if it is the
|
||
only chunk in unsorted bin. This helps promote locality for
|
||
runs of consecutive small requests. This is the only
|
||
exception to best-fit, and applies only when there is
|
||
no exact fit for a small chunk.
|
||
*/
|
||
|
||
if (in_smallbin_range (nb) &&
|
||
bck == unsorted_chunks (av) &&
|
||
victim == av->last_remainder &&
|
||
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
|
||
{
|
||
/* split and reattach remainder */
|
||
remainder_size = size - nb;
|
||
remainder = chunk_at_offset (victim, nb);
|
||
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
|
||
av->last_remainder = remainder;
|
||
remainder->bk = remainder->fd = unsorted_chunks (av);
|
||
if (!in_smallbin_range (remainder_size))
|
||
{
|
||
remainder->fd_nextsize = NULL;
|
||
remainder->bk_nextsize = NULL;
|
||
}
|
||
|
||
set_head (victim, nb | PREV_INUSE |
|
||
(av != &main_arena ? NON_MAIN_ARENA : 0));
|
||
set_head (remainder, remainder_size | PREV_INUSE);
|
||
set_foot (remainder, remainder_size);
|
||
|
||
check_malloced_chunk (av, victim, nb);
|
||
void *p = chunk2mem (victim);
|
||
alloc_perturb (p, bytes);
|
||
return p;
|
||
}
|
||
|
||
```
|
||
</details>
|
||
|
||
Якщо це було успішно, поверніть шматок і закінчіть, якщо ні, продовжуйте виконувати функцію...
|
||
|
||
#### якщо однаковий розмір
|
||
|
||
Продовжуйте видаляти шматок з бін, у разі якщо запитуваний розмір точно відповідає розміру шматка:
|
||
|
||
- Якщо tcache не заповнений, додайте його до tcache і продовжте вказувати, що є шматок tcache, який можна використовувати
|
||
- Якщо tcache заповнений, просто використайте його, повертаючи його
|
||
|
||
<details>
|
||
|
||
<summary><code>_int_malloc</code> несортований бін однакового розміру</summary>
|
||
```c
|
||
// From https://github.com/bminor/glibc/blob/master/malloc/malloc.c#L4126C11-L4157C14
|
||
|
||
/* remove from unsorted list */
|
||
unsorted_chunks (av)->bk = bck;
|
||
bck->fd = unsorted_chunks (av);
|
||
|
||
/* Take now instead of binning if exact fit */
|
||
|
||
if (size == nb)
|
||
{
|
||
set_inuse_bit_at_offset (victim, size);
|
||
if (av != &main_arena)
|
||
set_non_main_arena (victim);
|
||
#if USE_TCACHE
|
||
/* Fill cache first, return to user only if cache fills.
|
||
We may return one of these chunks later. */
|
||
if (tcache_nb > 0
|
||
&& tcache->counts[tc_idx] < mp_.tcache_count)
|
||
{
|
||
tcache_put (victim, tc_idx);
|
||
return_cached = 1;
|
||
continue;
|
||
}
|
||
else
|
||
{
|
||
#endif
|
||
check_malloced_chunk (av, victim, nb);
|
||
void *p = chunk2mem (victim);
|
||
alloc_perturb (p, bytes);
|
||
return p;
|
||
#if USE_TCACHE
|
||
}
|
||
#endif
|
||
}
|
||
|
||
```
|
||
</details>
|
||
|
||
Якщо шматок не повернуто або не додано до tcache, продовжуйте з кодом...
|
||
|
||
#### помістіть шматок у бін
|
||
|
||
Зберігайте перевірений шматок у малому біні або у великому біні відповідно до розміру шматка (підтримуючи великий бін у належному порядку).
|
||
|
||
Виконуються перевірки безпеки, щоб переконатися, що обидва списки з подвоєним зв'язком великого біна не пошкоджені:
|
||
|
||
- Якщо `fwd->bk_nextsize->fd_nextsize != fwd`: `malloc(): largebin double linked list corrupted (nextsize)`
|
||
- Якщо `fwd->bk->fd != fwd`: `malloc(): largebin double linked list corrupted (bk)`
|
||
|
||
<details>
|
||
|
||
<summary><code>_int_malloc</code> помістіть шматок у бін</summary>
|
||
```c
|
||
/* place chunk in bin */
|
||
|
||
if (in_smallbin_range (size))
|
||
{
|
||
victim_index = smallbin_index (size);
|
||
bck = bin_at (av, victim_index);
|
||
fwd = bck->fd;
|
||
}
|
||
else
|
||
{
|
||
victim_index = largebin_index (size);
|
||
bck = bin_at (av, victim_index);
|
||
fwd = bck->fd;
|
||
|
||
/* maintain large bins in sorted order */
|
||
if (fwd != bck)
|
||
{
|
||
/* Or with inuse bit to speed comparisons */
|
||
size |= PREV_INUSE;
|
||
/* if smaller than smallest, bypass loop below */
|
||
assert (chunk_main_arena (bck->bk));
|
||
if ((unsigned long) (size)
|
||
< (unsigned long) chunksize_nomask (bck->bk))
|
||
{
|
||
fwd = bck;
|
||
bck = bck->bk;
|
||
|
||
victim->fd_nextsize = fwd->fd;
|
||
victim->bk_nextsize = fwd->fd->bk_nextsize;
|
||
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
|
||
}
|
||
else
|
||
{
|
||
assert (chunk_main_arena (fwd));
|
||
while ((unsigned long) size < chunksize_nomask (fwd))
|
||
{
|
||
fwd = fwd->fd_nextsize;
|
||
assert (chunk_main_arena (fwd));
|
||
}
|
||
|
||
if ((unsigned long) size
|
||
== (unsigned long) chunksize_nomask (fwd))
|
||
/* Always insert in the second position. */
|
||
fwd = fwd->fd;
|
||
else
|
||
{
|
||
victim->fd_nextsize = fwd;
|
||
victim->bk_nextsize = fwd->bk_nextsize;
|
||
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
|
||
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
|
||
fwd->bk_nextsize = victim;
|
||
victim->bk_nextsize->fd_nextsize = victim;
|
||
}
|
||
bck = fwd->bk;
|
||
if (bck->fd != fwd)
|
||
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
|
||
}
|
||
}
|
||
else
|
||
victim->fd_nextsize = victim->bk_nextsize = victim;
|
||
}
|
||
|
||
mark_bin (av, victim_index);
|
||
victim->bk = bck;
|
||
victim->fd = fwd;
|
||
fwd->bk = victim;
|
||
bck->fd = victim;
|
||
```
|
||
</details>
|
||
|
||
#### `_int_malloc` обмеження
|
||
|
||
На цьому етапі, якщо якийсь шматок був збережений у tcache, який можна використовувати, і обмеження досягнуто, просто **поверніть шматок з tcache**.
|
||
|
||
Більше того, якщо **MAX_ITERS** досягнуто, вийдіть з циклу і отримайте шматок іншим способом (top chunk).
|
||
|
||
Якщо `return_cached` було встановлено, просто поверніть шматок з tcache, щоб уникнути більших пошуків.
|
||
|
||
<details>
|
||
|
||
<summary><code>_int_malloc</code> обмеження</summary>
|
||
```c
|
||
// From https://github.com/bminor/glibc/blob/master/malloc/malloc.c#L4227C1-L4250C7
|
||
|
||
#if USE_TCACHE
|
||
/* If we've processed as many chunks as we're allowed while
|
||
filling the cache, return one of the cached ones. */
|
||
++tcache_unsorted_count;
|
||
if (return_cached
|
||
&& mp_.tcache_unsorted_limit > 0
|
||
&& tcache_unsorted_count > mp_.tcache_unsorted_limit)
|
||
{
|
||
return tcache_get (tc_idx);
|
||
}
|
||
#endif
|
||
|
||
#define MAX_ITERS 10000
|
||
if (++iters >= MAX_ITERS)
|
||
break;
|
||
}
|
||
|
||
#if USE_TCACHE
|
||
/* If all the small chunks we found ended up cached, return one now. */
|
||
if (return_cached)
|
||
{
|
||
return tcache_get (tc_idx);
|
||
}
|
||
#endif
|
||
```
|
||
</details>
|
||
|
||
Якщо межі не досягнуті, продовжуйте з кодом...
|
||
|
||
### Велика бін (за індексом)
|
||
|
||
Якщо запит великий (не в малому біні) і ми ще не повернули жоден шматок, отримайте **індекс** запитуваного розміру у **великому біні**, перевірте, чи **не порожній** або чи **найбільший шматок у цьому біні більший** за запитуваний розмір, і в цьому випадку знайдіть **найменший шматок, який можна використовувати** для запитуваного розміру.
|
||
|
||
Якщо залишковий простір від остаточно використаного шматка може бути новим шматком, додайте його до незасортованого біна, і last_reminder оновлюється.
|
||
|
||
Перевірка безпеки виконується при додаванні залишку до незасортованого біна:
|
||
|
||
- `bck->fd-> bk != bck`: `malloc(): пошкоджені незасортовані шматки`
|
||
|
||
<details>
|
||
|
||
<summary><code>_int_malloc</code> Великий бін (за індексом)</summary>
|
||
```c
|
||
// From https://github.com/bminor/glibc/blob/master/malloc/malloc.c#L4252C7-L4317C10
|
||
|
||
/*
|
||
If a large request, scan through the chunks of current bin in
|
||
sorted order to find smallest that fits. Use the skip list for this.
|
||
*/
|
||
|
||
if (!in_smallbin_range (nb))
|
||
{
|
||
bin = bin_at (av, idx);
|
||
|
||
/* skip scan if empty or largest chunk is too small */
|
||
if ((victim = first (bin)) != bin
|
||
&& (unsigned long) chunksize_nomask (victim)
|
||
>= (unsigned long) (nb))
|
||
{
|
||
victim = victim->bk_nextsize;
|
||
while (((unsigned long) (size = chunksize (victim)) <
|
||
(unsigned long) (nb)))
|
||
victim = victim->bk_nextsize;
|
||
|
||
/* Avoid removing the first entry for a size so that the skip
|
||
list does not have to be rerouted. */
|
||
if (victim != last (bin)
|
||
&& chunksize_nomask (victim)
|
||
== chunksize_nomask (victim->fd))
|
||
victim = victim->fd;
|
||
|
||
remainder_size = size - nb;
|
||
unlink_chunk (av, victim);
|
||
|
||
/* Exhaust */
|
||
if (remainder_size < MINSIZE)
|
||
{
|
||
set_inuse_bit_at_offset (victim, size);
|
||
if (av != &main_arena)
|
||
set_non_main_arena (victim);
|
||
}
|
||
/* Split */
|
||
else
|
||
{
|
||
remainder = chunk_at_offset (victim, nb);
|
||
/* We cannot assume the unsorted list is empty and therefore
|
||
have to perform a complete insert here. */
|
||
bck = unsorted_chunks (av);
|
||
fwd = bck->fd;
|
||
if (__glibc_unlikely (fwd->bk != bck))
|
||
malloc_printerr ("malloc(): corrupted unsorted chunks");
|
||
last_re->bk = bck;
|
||
remainder->fd = fwd;
|
||
bck->fd = remainder;
|
||
fwd->bk = remainder;
|
||
if (!in_smallbin_range (remainder_size))
|
||
{
|
||
remainder->fd_nextsize = NULL;
|
||
remainder->bk_nextsize = NULL;
|
||
}
|
||
set_head (victim, nb | PREV_INUSE |
|
||
(av != &main_arena ? NON_MAIN_ARENA : 0));
|
||
set_head (remainder, remainder_size | PREV_INUSE);
|
||
set_foot (remainder, remainder_size);
|
||
}
|
||
check_malloced_chunk (av, victim, nb);
|
||
void *p = chunk2mem (victim);
|
||
alloc_perturb (p, bytes);
|
||
return p;
|
||
}
|
||
}
|
||
```
|
||
</details>
|
||
|
||
Якщо шматок не підходить для цього, продовжуйте
|
||
|
||
### Велика корзина (наступна більша)
|
||
|
||
Якщо в точно великій корзині не було жодного шматка, який можна було б використати, почніть перебір усіх наступних великих корзин (починаючи з найближчої більшої) до тих пір, поки не буде знайдено один (якщо є).
|
||
|
||
Залишок розділеного шматка додається в неупорядковану корзину, last_reminder оновлюється, і виконується та ж сама перевірка безпеки:
|
||
|
||
- `bck->fd-> bk != bck`: `malloc(): corrupted unsorted chunks2`
|
||
|
||
<details>
|
||
|
||
<summary><code>_int_malloc</code> Велика корзина (наступна більша)</summary>
|
||
```c
|
||
// From https://github.com/bminor/glibc/blob/master/malloc/malloc.c#L4319C7-L4425C10
|
||
|
||
/*
|
||
Search for a chunk by scanning bins, starting with next largest
|
||
bin. This search is strictly by best-fit; i.e., the smallest
|
||
(with ties going to approximately the least recently used) chunk
|
||
that fits is selected.
|
||
|
||
The bitmap avoids needing to check that most blocks are nonempty.
|
||
The particular case of skipping all bins during warm-up phases
|
||
when no chunks have been returned yet is faster than it might look.
|
||
*/
|
||
|
||
++idx;
|
||
bin = bin_at (av, idx);
|
||
block = idx2block (idx);
|
||
map = av->binmap[block];
|
||
bit = idx2bit (idx);
|
||
|
||
for (;; )
|
||
{
|
||
/* Skip rest of block if there are no more set bits in this block. */
|
||
if (bit > map || bit == 0)
|
||
{
|
||
do
|
||
{
|
||
if (++block >= BINMAPSIZE) /* out of bins */
|
||
goto use_top;
|
||
}
|
||
while ((map = av->binmap[block]) == 0);
|
||
|
||
bin = bin_at (av, (block << BINMAPSHIFT));
|
||
bit = 1;
|
||
}
|
||
|
||
/* Advance to bin with set bit. There must be one. */
|
||
while ((bit & map) == 0)
|
||
{
|
||
bin = next_bin (bin);
|
||
bit <<= 1;
|
||
assert (bit != 0);
|
||
}
|
||
|
||
/* Inspect the bin. It is likely to be non-empty */
|
||
victim = last (bin);
|
||
|
||
/* If a false alarm (empty bin), clear the bit. */
|
||
if (victim == bin)
|
||
{
|
||
av->binmap[block] = map &= ~bit; /* Write through */
|
||
bin = next_bin (bin);
|
||
bit <<= 1;
|
||
}
|
||
|
||
else
|
||
{
|
||
size = chunksize (victim);
|
||
|
||
/* We know the first chunk in this bin is big enough to use. */
|
||
assert ((unsigned long) (size) >= (unsigned long) (nb));
|
||
|
||
remainder_size = size - nb;
|
||
|
||
/* unlink */
|
||
unlink_chunk (av, victim);
|
||
|
||
/* Exhaust */
|
||
if (remainder_size < MINSIZE)
|
||
{
|
||
set_inuse_bit_at_offset (victim, size);
|
||
if (av != &main_arena)
|
||
set_non_main_arena (victim);
|
||
}
|
||
|
||
/* Split */
|
||
else
|
||
{
|
||
remainder = chunk_at_offset (victim, nb);
|
||
|
||
/* We cannot assume the unsorted list is empty and therefore
|
||
have to perform a complete insert here. */
|
||
bck = unsorted_chunks (av);
|
||
fwd = bck->fd;
|
||
if (__glibc_unlikely (fwd->bk != bck))
|
||
malloc_printerr ("malloc(): corrupted unsorted chunks 2");
|
||
remainder->bk = bck;
|
||
remainder->fd = fwd;
|
||
bck->fd = remainder;
|
||
fwd->bk = remainder;
|
||
|
||
/* advertise as last remainder */
|
||
if (in_smallbin_range (nb))
|
||
av->last_remainder = remainder;
|
||
if (!in_smallbin_range (remainder_size))
|
||
{
|
||
remainder->fd_nextsize = NULL;
|
||
remainder->bk_nextsize = NULL;
|
||
}
|
||
set_head (victim, nb | PREV_INUSE |
|
||
(av != &main_arena ? NON_MAIN_ARENA : 0));
|
||
set_head (remainder, remainder_size | PREV_INUSE);
|
||
set_foot (remainder, remainder_size);
|
||
}
|
||
check_malloced_chunk (av, victim, nb);
|
||
void *p = chunk2mem (victim);
|
||
alloc_perturb (p, bytes);
|
||
return p;
|
||
}
|
||
}
|
||
```
|
||
</details>
|
||
|
||
### Top Chunk
|
||
|
||
На цьому етапі час отримати новий шматок з Top chunk (якщо він достатньо великий).
|
||
|
||
Він починається з перевірки безпеки, щоб переконатися, що розмір шматка не занадто великий (пошкоджений):
|
||
|
||
- `chunksize(av->top) > av->system_mem`: `malloc(): corrupted top size`
|
||
|
||
Тоді він використає простір top chunk, якщо він достатньо великий, щоб створити шматок запитуваного розміру.\
|
||
Якщо ні, якщо є швидкі шматки, об'єднайте їх і спробуйте знову.\
|
||
Нарешті, якщо недостатньо місця, використовуйте `sysmalloc`, щоб виділити достатній розмір.
|
||
|
||
<details>
|
||
|
||
<summary><code>_int_malloc</code> Top chunk</summary>
|
||
```c
|
||
use_top:
|
||
/*
|
||
If large enough, split off the chunk bordering the end of memory
|
||
(held in av->top). Note that this is in accord with the best-fit
|
||
search rule. In effect, av->top is treated as larger (and thus
|
||
less well fitting) than any other available chunk since it can
|
||
be extended to be as large as necessary (up to system
|
||
limitations).
|
||
|
||
We require that av->top always exists (i.e., has size >=
|
||
MINSIZE) after initialization, so if it would otherwise be
|
||
exhausted by current request, it is replenished. (The main
|
||
reason for ensuring it exists is that we may need MINSIZE space
|
||
to put in fenceposts in sysmalloc.)
|
||
*/
|
||
|
||
victim = av->top;
|
||
size = chunksize (victim);
|
||
|
||
if (__glibc_unlikely (size > av->system_mem))
|
||
malloc_printerr ("malloc(): corrupted top size");
|
||
|
||
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
|
||
{
|
||
remainder_size = size - nb;
|
||
remainder = chunk_at_offset (victim, nb);
|
||
av->top = remainder;
|
||
set_head (victim, nb | PREV_INUSE |
|
||
(av != &main_arena ? NON_MAIN_ARENA : 0));
|
||
set_head (remainder, remainder_size | PREV_INUSE);
|
||
|
||
check_malloced_chunk (av, victim, nb);
|
||
void *p = chunk2mem (victim);
|
||
alloc_perturb (p, bytes);
|
||
return p;
|
||
}
|
||
|
||
/* When we are using atomic ops to free fast chunks we can get
|
||
here for all block sizes. */
|
||
else if (atomic_load_relaxed (&av->have_fastchunks))
|
||
{
|
||
malloc_consolidate (av);
|
||
/* restore original bin index */
|
||
if (in_smallbin_range (nb))
|
||
idx = smallbin_index (nb);
|
||
else
|
||
idx = largebin_index (nb);
|
||
}
|
||
|
||
/*
|
||
Otherwise, relay to handle system-dependent cases
|
||
*/
|
||
else
|
||
{
|
||
void *p = sysmalloc (nb, av);
|
||
if (p != NULL)
|
||
alloc_perturb (p, bytes);
|
||
return p;
|
||
}
|
||
}
|
||
}
|
||
|
||
```
|
||
</details>
|
||
|
||
## sysmalloc
|
||
|
||
### sysmalloc початок
|
||
|
||
Якщо arena є null або запитуваний розмір занадто великий (і залишилися дозволені mmaps), використовуйте `sysmalloc_mmap` для виділення простору та повернення його.
|
||
|
||
<details>
|
||
|
||
<summary>sysmalloc початок</summary>
|
||
```c
|
||
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L2531
|
||
|
||
/*
|
||
sysmalloc handles malloc cases requiring more memory from the system.
|
||
On entry, it is assumed that av->top does not have enough
|
||
space to service request for nb bytes, thus requiring that av->top
|
||
be extended or replaced.
|
||
*/
|
||
|
||
static void *
|
||
sysmalloc (INTERNAL_SIZE_T nb, mstate av)
|
||
{
|
||
mchunkptr old_top; /* incoming value of av->top */
|
||
INTERNAL_SIZE_T old_size; /* its size */
|
||
char *old_end; /* its end address */
|
||
|
||
long size; /* arg to first MORECORE or mmap call */
|
||
char *brk; /* return value from MORECORE */
|
||
|
||
long correction; /* arg to 2nd MORECORE call */
|
||
char *snd_brk; /* 2nd return val */
|
||
|
||
INTERNAL_SIZE_T front_misalign; /* unusable bytes at front of new space */
|
||
INTERNAL_SIZE_T end_misalign; /* partial page left at end of new space */
|
||
char *aligned_brk; /* aligned offset into brk */
|
||
|
||
mchunkptr p; /* the allocated/returned chunk */
|
||
mchunkptr remainder; /* remainder from allocation */
|
||
unsigned long remainder_size; /* its size */
|
||
|
||
|
||
size_t pagesize = GLRO (dl_pagesize);
|
||
bool tried_mmap = false;
|
||
|
||
|
||
/*
|
||
If have mmap, and the request size meets the mmap threshold, and
|
||
the system supports mmap, and there are few enough currently
|
||
allocated mmapped regions, try to directly map this request
|
||
rather than expanding top.
|
||
*/
|
||
|
||
if (av == NULL
|
||
|| ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
|
||
&& (mp_.n_mmaps < mp_.n_mmaps_max)))
|
||
{
|
||
char *mm;
|
||
if (mp_.hp_pagesize > 0 && nb >= mp_.hp_pagesize)
|
||
{
|
||
/* There is no need to issue the THP madvise call if Huge Pages are
|
||
used directly. */
|
||
mm = sysmalloc_mmap (nb, mp_.hp_pagesize, mp_.hp_flags, av);
|
||
if (mm != MAP_FAILED)
|
||
return mm;
|
||
}
|
||
mm = sysmalloc_mmap (nb, pagesize, 0, av);
|
||
if (mm != MAP_FAILED)
|
||
return mm;
|
||
tried_mmap = true;
|
||
}
|
||
|
||
/* There are no usable arenas and mmap also failed. */
|
||
if (av == NULL)
|
||
return 0;
|
||
```
|
||
</details>
|
||
|
||
### sysmalloc перевірки
|
||
|
||
Він починається з отримання інформації про старий верхній шматок і перевіряє, що деякі з наступних умов є істинними:
|
||
|
||
- Старий розмір купи дорівнює 0 (нова купа)
|
||
- Розмір попередньої купи більший за MINSIZE і старий верхній шматок використовується
|
||
- Купа вирівняна по розміру сторінки (0x1000, тому нижні 12 біт повинні бути 0)
|
||
|
||
Потім він також перевіряє, що:
|
||
|
||
- Старий розмір не має достатньо місця для створення шматка для запитуваного розміру
|
||
|
||
<details>
|
||
|
||
<summary>sysmalloc перевірки</summary>
|
||
```c
|
||
/* Record incoming configuration of top */
|
||
|
||
old_top = av->top;
|
||
old_size = chunksize (old_top);
|
||
old_end = (char *) (chunk_at_offset (old_top, old_size));
|
||
|
||
brk = snd_brk = (char *) (MORECORE_FAILURE);
|
||
|
||
/*
|
||
If not the first time through, we require old_size to be
|
||
at least MINSIZE and to have prev_inuse set.
|
||
*/
|
||
|
||
assert ((old_top == initial_top (av) && old_size == 0) ||
|
||
((unsigned long) (old_size) >= MINSIZE &&
|
||
prev_inuse (old_top) &&
|
||
((unsigned long) old_end & (pagesize - 1)) == 0));
|
||
|
||
/* Precondition: not enough current space to satisfy nb request */
|
||
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
|
||
```
|
||
</details>
|
||
|
||
### sysmalloc не основна арена
|
||
|
||
Спочатку він спробує **розширити** попередній хіп для цього хіпа. Якщо це неможливо, спробуйте **виділити новий хіп** і оновити вказівники, щоб мати можливість його використовувати.\
|
||
Нарешті, якщо це не спрацювало, спробуйте викликати **`sysmalloc_mmap`**.
|
||
|
||
<details>
|
||
|
||
<summary>sysmalloc не основна арена</summary>
|
||
```c
|
||
if (av != &main_arena)
|
||
{
|
||
heap_info *old_heap, *heap;
|
||
size_t old_heap_size;
|
||
|
||
/* First try to extend the current heap. */
|
||
old_heap = heap_for_ptr (old_top);
|
||
old_heap_size = old_heap->size;
|
||
if ((long) (MINSIZE + nb - old_size) > 0
|
||
&& grow_heap (old_heap, MINSIZE + nb - old_size) == 0)
|
||
{
|
||
av->system_mem += old_heap->size - old_heap_size;
|
||
set_head (old_top, (((char *) old_heap + old_heap->size) - (char *) old_top)
|
||
| PREV_INUSE);
|
||
}
|
||
else if ((heap = new_heap (nb + (MINSIZE + sizeof (*heap)), mp_.top_pad)))
|
||
{
|
||
/* Use a newly allocated heap. */
|
||
heap->ar_ptr = av;
|
||
heap->prev = old_heap;
|
||
av->system_mem += heap->size;
|
||
/* Set up the new top. */
|
||
top (av) = chunk_at_offset (heap, sizeof (*heap));
|
||
set_head (top (av), (heap->size - sizeof (*heap)) | PREV_INUSE);
|
||
|
||
/* Setup fencepost and free the old top chunk with a multiple of
|
||
MALLOC_ALIGNMENT in size. */
|
||
/* The fencepost takes at least MINSIZE bytes, because it might
|
||
become the top chunk again later. Note that a footer is set
|
||
up, too, although the chunk is marked in use. */
|
||
old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
|
||
set_head (chunk_at_offset (old_top, old_size + CHUNK_HDR_SZ),
|
||
0 | PREV_INUSE);
|
||
if (old_size >= MINSIZE)
|
||
{
|
||
set_head (chunk_at_offset (old_top, old_size),
|
||
CHUNK_HDR_SZ | PREV_INUSE);
|
||
set_foot (chunk_at_offset (old_top, old_size), CHUNK_HDR_SZ);
|
||
set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
|
||
_int_free (av, old_top, 1);
|
||
}
|
||
else
|
||
{
|
||
set_head (old_top, (old_size + CHUNK_HDR_SZ) | PREV_INUSE);
|
||
set_foot (old_top, (old_size + CHUNK_HDR_SZ));
|
||
}
|
||
}
|
||
else if (!tried_mmap)
|
||
{
|
||
/* We can at least try to use to mmap memory. If new_heap fails
|
||
it is unlikely that trying to allocate huge pages will
|
||
succeed. */
|
||
char *mm = sysmalloc_mmap (nb, pagesize, 0, av);
|
||
if (mm != MAP_FAILED)
|
||
return mm;
|
||
}
|
||
}
|
||
```
|
||
</details>
|
||
|
||
### sysmalloc основна арена
|
||
|
||
Він починає розраховувати необхідну кількість пам'яті. Він почне з запиту на безперервну пам'ять, тому в цьому випадку буде можливим використати стару невикористану пам'ять. Також виконуються деякі операції вирівнювання.
|
||
|
||
<details>
|
||
|
||
<summary>sysmalloc основна арена</summary>
|
||
```c
|
||
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L2665C1-L2713C10
|
||
|
||
else /* av == main_arena */
|
||
|
||
|
||
{ /* Request enough space for nb + pad + overhead */
|
||
size = nb + mp_.top_pad + MINSIZE;
|
||
|
||
/*
|
||
If contiguous, we can subtract out existing space that we hope to
|
||
combine with new space. We add it back later only if
|
||
we don't actually get contiguous space.
|
||
*/
|
||
|
||
if (contiguous (av))
|
||
size -= old_size;
|
||
|
||
/*
|
||
Round to a multiple of page size or huge page size.
|
||
If MORECORE is not contiguous, this ensures that we only call it
|
||
with whole-page arguments. And if MORECORE is contiguous and
|
||
this is not first time through, this preserves page-alignment of
|
||
previous calls. Otherwise, we correct to page-align below.
|
||
*/
|
||
|
||
#ifdef MADV_HUGEPAGE
|
||
/* Defined in brk.c. */
|
||
extern void *__curbrk;
|
||
if (__glibc_unlikely (mp_.thp_pagesize != 0))
|
||
{
|
||
uintptr_t top = ALIGN_UP ((uintptr_t) __curbrk + size,
|
||
mp_.thp_pagesize);
|
||
size = top - (uintptr_t) __curbrk;
|
||
}
|
||
else
|
||
#endif
|
||
size = ALIGN_UP (size, GLRO(dl_pagesize));
|
||
|
||
/*
|
||
Don't try to call MORECORE if argument is so big as to appear
|
||
negative. Note that since mmap takes size_t arg, it may succeed
|
||
below even if we cannot call MORECORE.
|
||
*/
|
||
|
||
if (size > 0)
|
||
{
|
||
brk = (char *) (MORECORE (size));
|
||
if (brk != (char *) (MORECORE_FAILURE))
|
||
madvise_thp (brk, size);
|
||
LIBC_PROBE (memory_sbrk_more, 2, brk, size);
|
||
}
|
||
```
|
||
</details>
|
||
|
||
### sysmalloc основна арена попередня помилка 1
|
||
|
||
Якщо попереднє повернене `MORECORE_FAILURE`, спробуйте знову виділити пам'ять, використовуючи `sysmalloc_mmap_fallback`
|
||
|
||
<details>
|
||
|
||
<summary><code>sysmalloc</code> основна арена попередня помилка 1</summary>
|
||
```c
|
||
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L2715C7-L2740C10
|
||
|
||
if (brk == (char *) (MORECORE_FAILURE))
|
||
{
|
||
/*
|
||
If have mmap, try using it as a backup when MORECORE fails or
|
||
cannot be used. This is worth doing on systems that have "holes" in
|
||
address space, so sbrk cannot extend to give contiguous space, but
|
||
space is available elsewhere. Note that we ignore mmap max count
|
||
and threshold limits, since the space will not be used as a
|
||
segregated mmap region.
|
||
*/
|
||
|
||
char *mbrk = MAP_FAILED;
|
||
if (mp_.hp_pagesize > 0)
|
||
mbrk = sysmalloc_mmap_fallback (&size, nb, old_size,
|
||
mp_.hp_pagesize, mp_.hp_pagesize,
|
||
mp_.hp_flags, av);
|
||
if (mbrk == MAP_FAILED)
|
||
mbrk = sysmalloc_mmap_fallback (&size, nb, old_size, MMAP_AS_MORECORE_SIZE,
|
||
pagesize, 0, av);
|
||
if (mbrk != MAP_FAILED)
|
||
{
|
||
/* We do not need, and cannot use, another sbrk call to find end */
|
||
brk = mbrk;
|
||
snd_brk = brk + size;
|
||
}
|
||
}
|
||
```
|
||
</details>
|
||
|
||
### sysmalloc основна арена продовження
|
||
|
||
Якщо попереднє не повернуло `MORECORE_FAILURE`, якщо це спрацювало, створіть деякі вирівнювання:
|
||
|
||
<details>
|
||
|
||
<summary>sysmalloc основна арена попередня помилка 2</summary>
|
||
```c
|
||
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L2742
|
||
|
||
if (brk != (char *) (MORECORE_FAILURE))
|
||
{
|
||
if (mp_.sbrk_base == 0)
|
||
mp_.sbrk_base = brk;
|
||
av->system_mem += size;
|
||
|
||
/*
|
||
If MORECORE extends previous space, we can likewise extend top size.
|
||
*/
|
||
|
||
if (brk == old_end && snd_brk == (char *) (MORECORE_FAILURE))
|
||
set_head (old_top, (size + old_size) | PREV_INUSE);
|
||
|
||
else if (contiguous (av) && old_size && brk < old_end)
|
||
/* Oops! Someone else killed our space.. Can't touch anything. */
|
||
malloc_printerr ("break adjusted to free malloc space");
|
||
|
||
/*
|
||
Otherwise, make adjustments:
|
||
|
||
* If the first time through or noncontiguous, we need to call sbrk
|
||
just to find out where the end of memory lies.
|
||
|
||
* We need to ensure that all returned chunks from malloc will meet
|
||
MALLOC_ALIGNMENT
|
||
|
||
* If there was an intervening foreign sbrk, we need to adjust sbrk
|
||
request size to account for fact that we will not be able to
|
||
combine new space with existing space in old_top.
|
||
|
||
* Almost all systems internally allocate whole pages at a time, in
|
||
which case we might as well use the whole last page of request.
|
||
So we allocate enough more memory to hit a page boundary now,
|
||
which in turn causes future contiguous calls to page-align.
|
||
*/
|
||
|
||
else
|
||
{
|
||
front_misalign = 0;
|
||
end_misalign = 0;
|
||
correction = 0;
|
||
aligned_brk = brk;
|
||
|
||
/* handle contiguous cases */
|
||
if (contiguous (av))
|
||
{
|
||
/* Count foreign sbrk as system_mem. */
|
||
if (old_size)
|
||
av->system_mem += brk - old_end;
|
||
|
||
/* Guarantee alignment of first new chunk made from this space */
|
||
|
||
front_misalign = (INTERNAL_SIZE_T) chunk2mem (brk) & MALLOC_ALIGN_MASK;
|
||
if (front_misalign > 0)
|
||
{
|
||
/*
|
||
Skip over some bytes to arrive at an aligned position.
|
||
We don't need to specially mark these wasted front bytes.
|
||
They will never be accessed anyway because
|
||
prev_inuse of av->top (and any chunk created from its start)
|
||
is always true after initialization.
|
||
*/
|
||
|
||
correction = MALLOC_ALIGNMENT - front_misalign;
|
||
aligned_brk += correction;
|
||
}
|
||
|
||
/*
|
||
If this isn't adjacent to existing space, then we will not
|
||
be able to merge with old_top space, so must add to 2nd request.
|
||
*/
|
||
|
||
correction += old_size;
|
||
|
||
/* Extend the end address to hit a page boundary */
|
||
end_misalign = (INTERNAL_SIZE_T) (brk + size + correction);
|
||
correction += (ALIGN_UP (end_misalign, pagesize)) - end_misalign;
|
||
|
||
assert (correction >= 0);
|
||
snd_brk = (char *) (MORECORE (correction));
|
||
|
||
/*
|
||
If can't allocate correction, try to at least find out current
|
||
brk. It might be enough to proceed without failing.
|
||
|
||
Note that if second sbrk did NOT fail, we assume that space
|
||
is contiguous with first sbrk. This is a safe assumption unless
|
||
program is multithreaded but doesn't use locks and a foreign sbrk
|
||
occurred between our first and second calls.
|
||
*/
|
||
|
||
if (snd_brk == (char *) (MORECORE_FAILURE))
|
||
{
|
||
correction = 0;
|
||
snd_brk = (char *) (MORECORE (0));
|
||
}
|
||
else
|
||
madvise_thp (snd_brk, correction);
|
||
}
|
||
|
||
/* handle non-contiguous cases */
|
||
else
|
||
{
|
||
if (MALLOC_ALIGNMENT == CHUNK_HDR_SZ)
|
||
/* MORECORE/mmap must correctly align */
|
||
assert (((unsigned long) chunk2mem (brk) & MALLOC_ALIGN_MASK) == 0);
|
||
else
|
||
{
|
||
front_misalign = (INTERNAL_SIZE_T) chunk2mem (brk) & MALLOC_ALIGN_MASK;
|
||
if (front_misalign > 0)
|
||
{
|
||
/*
|
||
Skip over some bytes to arrive at an aligned position.
|
||
We don't need to specially mark these wasted front bytes.
|
||
They will never be accessed anyway because
|
||
prev_inuse of av->top (and any chunk created from its start)
|
||
is always true after initialization.
|
||
*/
|
||
|
||
aligned_brk += MALLOC_ALIGNMENT - front_misalign;
|
||
}
|
||
}
|
||
|
||
/* Find out current end of memory */
|
||
if (snd_brk == (char *) (MORECORE_FAILURE))
|
||
{
|
||
snd_brk = (char *) (MORECORE (0));
|
||
}
|
||
}
|
||
|
||
/* Adjust top based on results of second sbrk */
|
||
if (snd_brk != (char *) (MORECORE_FAILURE))
|
||
{
|
||
av->top = (mchunkptr) aligned_brk;
|
||
set_head (av->top, (snd_brk - aligned_brk + correction) | PREV_INUSE);
|
||
av->system_mem += correction;
|
||
|
||
/*
|
||
If not the first time through, we either have a
|
||
gap due to foreign sbrk or a non-contiguous region. Insert a
|
||
double fencepost at old_top to prevent consolidation with space
|
||
we don't own. These fenceposts are artificial chunks that are
|
||
marked as inuse and are in any case too small to use. We need
|
||
two to make sizes and alignments work out.
|
||
*/
|
||
|
||
if (old_size != 0)
|
||
{
|
||
/*
|
||
Shrink old_top to insert fenceposts, keeping size a
|
||
multiple of MALLOC_ALIGNMENT. We know there is at least
|
||
enough space in old_top to do this.
|
||
*/
|
||
old_size = (old_size - 2 * CHUNK_HDR_SZ) & ~MALLOC_ALIGN_MASK;
|
||
set_head (old_top, old_size | PREV_INUSE);
|
||
|
||
/*
|
||
Note that the following assignments completely overwrite
|
||
old_top when old_size was previously MINSIZE. This is
|
||
intentional. We need the fencepost, even if old_top otherwise gets
|
||
lost.
|
||
*/
|
||
set_head (chunk_at_offset (old_top, old_size),
|
||
CHUNK_HDR_SZ | PREV_INUSE);
|
||
set_head (chunk_at_offset (old_top,
|
||
old_size + CHUNK_HDR_SZ),
|
||
CHUNK_HDR_SZ | PREV_INUSE);
|
||
|
||
/* If possible, release the rest. */
|
||
if (old_size >= MINSIZE)
|
||
{
|
||
_int_free (av, old_top, 1);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} /* if (av != &main_arena) */
|
||
```
|
||
</details>
|
||
|
||
### sysmalloc finale
|
||
|
||
Завершіть виділення, оновивши інформацію про арену.
|
||
```c
|
||
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L2921C3-L2943C12
|
||
|
||
if ((unsigned long) av->system_mem > (unsigned long) (av->max_system_mem))
|
||
av->max_system_mem = av->system_mem;
|
||
check_malloc_state (av);
|
||
|
||
/* finally, do the allocation */
|
||
p = av->top;
|
||
size = chunksize (p);
|
||
|
||
/* check that one of the above allocation paths succeeded */
|
||
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
|
||
{
|
||
remainder_size = size - nb;
|
||
remainder = chunk_at_offset (p, nb);
|
||
av->top = remainder;
|
||
set_head (p, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
|
||
set_head (remainder, remainder_size | PREV_INUSE);
|
||
check_malloced_chunk (av, p, nb);
|
||
return chunk2mem (p);
|
||
}
|
||
|
||
/* catch all failure paths */
|
||
__set_errno (ENOMEM);
|
||
return 0;
|
||
```
|
||
## sysmalloc_mmap
|
||
|
||
<details>
|
||
|
||
<summary>код sysmalloc_mmap</summary>
|
||
```c
|
||
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L2392C1-L2481C2
|
||
|
||
static void *
|
||
sysmalloc_mmap (INTERNAL_SIZE_T nb, size_t pagesize, int extra_flags, mstate av)
|
||
{
|
||
long int size;
|
||
|
||
/*
|
||
Round up size to nearest page. For mmapped chunks, the overhead is one
|
||
SIZE_SZ unit larger than for normal chunks, because there is no
|
||
following chunk whose prev_size field could be used.
|
||
|
||
See the front_misalign handling below, for glibc there is no need for
|
||
further alignments unless we have have high alignment.
|
||
*/
|
||
if (MALLOC_ALIGNMENT == CHUNK_HDR_SZ)
|
||
size = ALIGN_UP (nb + SIZE_SZ, pagesize);
|
||
else
|
||
size = ALIGN_UP (nb + SIZE_SZ + MALLOC_ALIGN_MASK, pagesize);
|
||
|
||
/* Don't try if size wraps around 0. */
|
||
if ((unsigned long) (size) <= (unsigned long) (nb))
|
||
return MAP_FAILED;
|
||
|
||
char *mm = (char *) MMAP (0, size,
|
||
mtag_mmap_flags | PROT_READ | PROT_WRITE,
|
||
extra_flags);
|
||
if (mm == MAP_FAILED)
|
||
return mm;
|
||
|
||
#ifdef MAP_HUGETLB
|
||
if (!(extra_flags & MAP_HUGETLB))
|
||
madvise_thp (mm, size);
|
||
#endif
|
||
|
||
__set_vma_name (mm, size, " glibc: malloc");
|
||
|
||
/*
|
||
The offset to the start of the mmapped region is stored in the prev_size
|
||
field of the chunk. This allows us to adjust returned start address to
|
||
meet alignment requirements here and in memalign(), and still be able to
|
||
compute proper address argument for later munmap in free() and realloc().
|
||
*/
|
||
|
||
INTERNAL_SIZE_T front_misalign; /* unusable bytes at front of new space */
|
||
|
||
if (MALLOC_ALIGNMENT == CHUNK_HDR_SZ)
|
||
{
|
||
/* For glibc, chunk2mem increases the address by CHUNK_HDR_SZ and
|
||
MALLOC_ALIGN_MASK is CHUNK_HDR_SZ-1. Each mmap'ed area is page
|
||
aligned and therefore definitely MALLOC_ALIGN_MASK-aligned. */
|
||
assert (((INTERNAL_SIZE_T) chunk2mem (mm) & MALLOC_ALIGN_MASK) == 0);
|
||
front_misalign = 0;
|
||
}
|
||
else
|
||
front_misalign = (INTERNAL_SIZE_T) chunk2mem (mm) & MALLOC_ALIGN_MASK;
|
||
|
||
mchunkptr p; /* the allocated/returned chunk */
|
||
|
||
if (front_misalign > 0)
|
||
{
|
||
ptrdiff_t correction = MALLOC_ALIGNMENT - front_misalign;
|
||
p = (mchunkptr) (mm + correction);
|
||
set_prev_size (p, correction);
|
||
set_head (p, (size - correction) | IS_MMAPPED);
|
||
}
|
||
else
|
||
{
|
||
p = (mchunkptr) mm;
|
||
set_prev_size (p, 0);
|
||
set_head (p, size | IS_MMAPPED);
|
||
}
|
||
|
||
/* update statistics */
|
||
int new = atomic_fetch_add_relaxed (&mp_.n_mmaps, 1) + 1;
|
||
atomic_max (&mp_.max_n_mmaps, new);
|
||
|
||
unsigned long sum;
|
||
sum = atomic_fetch_add_relaxed (&mp_.mmapped_mem, size) + size;
|
||
atomic_max (&mp_.max_mmapped_mem, sum);
|
||
|
||
check_chunk (av, p);
|
||
|
||
return chunk2mem (p);
|
||
}
|
||
```
|
||
</details>
|
||
|
||
{{#include ../../../banners/hacktricks-training.md}}
|