mstate av;
mchunkptr oldtop, p;
INTERNAL_SIZE_T bytes, sz, csz, oldtopsize;
void *mem;
unsigned long clearsize;
unsigned long nclears;
INTERNAL_SIZE_T *d;
사용될 변수들을 선언해놓는다.
/* size_t is unsigned so the behavior on overflow is defined. */
bytes = n * elem_size;
#define HALF_INTERNAL_SIZE_T \
(((INTERNAL_SIZE_T) 1) << (8 * sizeof (INTERNAL_SIZE_T) / 2))
if (__builtin_expect ((n | elem_size) >= HALF_INTERNAL_SIZE_T, 0))
{
if (elem_size != 0 && bytes / elem_size != n)
{
__set_errno (ENOMEM);
return 0;
}
}
size검사를 하는것 같다. 자세하 내용은 더 공부해서 올리겠습니다. ㅠ.ㅠ
void *(*hook) (size_t, const void *) =
atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
{
sz = bytes;
mem = (*hook)(sz, RETURN_ADDRESS (0));
if (mem == 0)
return 0;
return memset (mem, 0, sz);
}
저 함수 hook이 malloc, free 등 여러가지 함수에 쓰이는데 hook 관련으로 좀 더 찾아보고 공부해서 올려보겠습니다.
if 문은 저 hook 함수가 오작동일으켰을경우인것 같다.
sz = bytes;
MAYBE_INIT_TCACHE ();
if (SINGLE_THREAD_P)
av = &main_arena;
else
arena_get (av, sz);
bytes는 요청한 크기인데 그것을 sz에 담는다.
단일 스레드이면 main_arena를 불러오고 그게아니라면 sz에 맞는 arena를 얻어오는 것 같다. 자세한 것은 arena.c분석에서 해봐야겠다.
if (av)
{
/* Check if we hand out the top chunk, in which case there may be no
need to clear. */
#if MORECORE_CLEARS
oldtop = top (av);
oldtopsize = chunksize (top (av));
# if MORECORE_CLEARS < 2
/* Only newly allocated memory is guaranteed to be cleared. */
if (av == &main_arena &&
oldtopsize < mp_.sbrk_base + av->max_system_mem - (char *) oldtop)
oldtopsize = (mp_.sbrk_base + av->max_system_mem - (char *) oldtop);
# endif
if (av != &main_arena)
{
heap_info *heap = heap_for_ptr (oldtop);
if (oldtopsize < (char *) heap + heap->mprotect_size - (char *) oldtop)
oldtopsize = (char *) heap + heap->mprotect_size - (char *) oldtop;
}
#endif
}
else
{
/* No usable arenas. */
oldtop = 0;
oldtopsize = 0;
}
av를 성공적으로, 즉 arena를 성공적으로 얻어오면 오는 조건문이다.
MORECORE에 관련된 조건문이므로, 시스템에게 메모리를 더 달라고 요청했을 때 생기는 top_chunk 크기 관련인 것 같다.
mem = _int_malloc (av, sz);
assert (!mem || chunk_is_mmapped (mem2chunk (mem)) ||
av == arena_for_chunk (mem2chunk (mem)));
얻어온 아레나에 메모리 하나를 할당한다.
그 메모리 주소가 잘못됬거나, mmap으로 할당된 청크이거나, 할당한 청크의 아레나가 우리가 얻어온 아레나와 일치하면 통과한다.
if (!SINGLE_THREAD_P)
{
if (mem == 0 && av != NULL)
{
LIBC_PROBE (memory_calloc_retry, 1, sz);
av = arena_get_retry (av, sz);
mem = _int_malloc (av, sz);
}
if (av != NULL)
__libc_lock_unlock (av->mutex);
}
싱글 스레드가 아닐때의 조건문이다.
메모리를 잘못얻어왔으면 새로 아레나를 할당받고, 메모리 할당을 시도한다.
그리고 av를 잘 얻어왔으면 어떤 잠금을 푸는 듯한 함수를 실행한다. 이 함수의 기능을 짐작해보기전에, malloc_state 구조체에
struct malloc_state
{
/* Serialize access. */
__libc_lock_define (, mutex);
...중략...
}
이런 함수가 보이는데 함수이름과 주석으로 보아 접근을 제한하는 용도 즉 lock하는 것으로 추정된다. 그러면 이제 감이 올건데 __libc_lock_unlock은 이 lock을 풀어주는 함수라고 짐작해볼 수 있다.
/* Allocation failed even after a retry. */
if (mem == 0)
return 0;
잘못된 메모리가 얻어지면 함수를 종료한다.
p = mem2chunk (mem);
/* Two optional cases in which clearing not necessary */
if (chunk_is_mmapped (p))
{
if (__builtin_expect (perturb_byte, 0))
return memset (mem, 0, sz);
return mem;
}
p에 mem - 2 * size_t 의 주소를 저장한다,
그 뒤 그 청크가 mmap()을 통해 할당되면조건문에 들어가는데
여기서 중요하게 봐야되는게 calloc()함수 내부인데도 불구하고 널로 초기화를 안해준 상태에서 주소를 return해주는 루틴이 있다는 것이다. 그 루틴을 타기 위해서는 저기있는 if(__builtin_expect(perturb_byte,0))가 거짓이 되면 된다.
그런데 저 perturb_byte는 perturb가 '교란하다'라는 뜻을 가지고 있는 것에서 저 값이 0이면 정상 그 외이면 비정상으로 처리하게 될 것이라고 추측할 수 있다. 즉 정상이라면 초기화 없이 주소를 반환하는 것이다.
___builtin_expect에 대해 더 궁금하시다면 제 글을 참조해보세요
csz = chunksize (p);
#if MORECORE_CLEARS
if (perturb_byte == 0 && (p == oldtop && csz > oldtopsize))
{
/* clear only the bytes from non-freshly-sbrked memory */
csz = oldtopsize;
}
#endif
이 부분은 perturb_bytes와 sbrk 등에 대해 더 공부한뒤에 올리겠습니다. ㅠ.ㅠ
/* Unroll clear of <= 36 bytes (72 if 8byte sizes). We know that
contents have an odd number of INTERNAL_SIZE_T-sized words;
minimally 3. */
d = (INTERNAL_SIZE_T *) mem;
clearsize = csz - SIZE_SZ;
nclears = clearsize / sizeof (INTERNAL_SIZE_T);
assert (nclears >= 3);
if (nclears > 9)
return memset (d, 0, clearsize);
else
{
*(d + 0) = 0;
*(d + 1) = 0;
*(d + 2) = 0;
if (nclears > 4)
{
*(d + 3) = 0;
*(d + 4) = 0;
if (nclears > 6)
{
*(d + 5) = 0;
*(d + 6) = 0;
if (nclears > 8)
{
*(d + 7) = 0;
*(d + 8) = 0;
}
}
}
}
return mem;
}
여기가 바로 대망의 초기화부분이다.
먼저 d에다가 메모리 주소를 저장한다.
그런뒤 clear할 크기를 청크 크기에서 size_sz를 빼줌으로서 구해준다. 내 생각에는 헤더 부분의 크기를 빼준것 같다.
nclears의 값을 clearsize를 size_t로 나누어서 구한다. 초기화를 편하게 하기 위해서인것 같다.
그 뒤는 NULL로 초기화하는 과정이다.
'힙(heap) > glibc' 카테고리의 다른 글
__libc_free함수 분석 (0) | 2019.08.09 |
---|---|
__libc_malloc함수 분석 (0) | 2019.08.09 |
__builtin_expect, likely, unlikely (0) | 2019.08.09 |
tcache 분석 (0) | 2019.08.08 |
힙 보안검사(지속적으로 추가 예정) (0) | 2019.08.05 |