Profile

youngsouk

youngsouk

__libc_calloc 함수 분석

  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