· KLDP.org · KLDP.net · KLDP Wiki · KLDP BBS ·
xmalloc

TODO: 아직 완전히 쓴 문서는 아닙니다. 수정하기 전에 CCodeSnippets를 읽어 주시기 바랍니다.

TODO: ...


일반적으로, 버그를 발생할 확률이 가장 높은 곳은, malloc(3), calloc(3), realloc(3), 또는 free(3)를 써서 메모리를 동적으로 얻고 돌려주는 부분입니다. 다음과 같은 충고를 기억하고 있으면 버그 발생 확률을 조금이나마 줄일 수 있습니다:
  • malloc(3)/calloc(3)/realloc(3)으로 얻은 메모리는 반드시 free(3)를 불러서 돌려준다.
  • malloc(3)/calloc(3)/realloc(3)을 부른 후, 반드시 리턴 값이 널인지 검사한다.
  • free(3)를 부른 다음, 돌려준 메모리는 읽거나 쓰지 않는다.
  • 이미 free(3)된 메모리를 다시 free(3)하지 않는다.
또한 동적으로 메모리를 다룰 때 발생하는 버그를 쉽게 잡을 수 있도록 여러가지 도구가 이미 나와 있습니다.

TODO: efence 등 소개

이 절에서 다룰 xmalloc은 malloc()/calloc()/realloc()/free()에 기능을 조금 추가하여, 나중에 디버깅 또는 성능을 개선하려 할 때 도움을 줄 수 있는 모듈입니다.

TODO: 데스트탑 환경에서는 거의 필요없다는 것을 알림...

원래 이 모듈은 Linux 시스템에서 쓰려고 만든 것이 아닙니다. Embedded device와 같이 제공되는 C 라이브러리가 상대적으로 좋은 디버깅 환경을 갖추기가 어렵기 때문에 디자인한 것입니다.

DeleteMe: calloc() 은 할당하는 memory 를 0 으로 초기화합니다. xcalloc() 도 초기화루틴이 필요하지 않을까요? DeleteMe: header 의 redzone 과 footer 의 redzone 은 그 역할이 동일한 것으로 보이는데 xfree() 함수에서 굳이 두 redzone 의 오류처리방식이 다른 이유가 있습니까?

...

xmalloc 모듈이 제공하는 가장 대표적인 함수는 다음과 같습니다:
void *xmalloc(size_t size);
void *xcalloc(size_t nmemb, size_t size);
void *xrealloc(void *ptr, size_t size);
void xfree(void *ptr);
각각의 함수가 하는 일은 malloc(3), calloc(3), realloc(3), free(3)이 하는 일과 같으며, 다음과 같은 기능을 추가적으로 제공합니다.
  1. malloc()에 크기를 0으로 주었을 때의 문제를 해결한다.
  2. 각각의 함수들이 몇 번씩 불렸는지 그 횟수를 제공한다.
  3. 제한 크기를 두고, 이 크기를 넘으면 메모리가 부족한 것처럼 흉내낸다.
  4. 동적으로 할당된 메모리의 크기를 제공한다.
  5. 동적으로 할당받은 메모리 크기에 벗어나는 접근(read/write)를 예방한다.
처음 기능은 매우 간단합니다. 인자가 0으로 들어오면 1로 바꾸면 됩니다. 두 번째 기능은 전역 변수를 몇 개 두면 간단히 해결됩니다. 즉 각각의 함수가 불려졌을 때, 해당하는 전역 변수의 값을 하나씩 증가시키면, 나중에 몇 번씩 불렸는지 쉽게 알 수 있습니다. 세번째 기능도 마찬가지로, 제한 크기를 어떤 전역 변수에 저장해 두고, 메모리를 할당하기 전에 체크하면 됩니다. 네번째 기능은 꽤 까다롭습니다. 일단 malloc(3)을 직접 만들지 않는한, 동적으로 얻은 어떤 메모리 블럭의 크기를 알아내는 것은 불가능합니다. 따라서 malloc(3)으로 블럭을 얻을 때, 실제 필요한 공간보다 조금 더 얻어낸 다음, 그 추가 부분에 이 블럭을 요청할 때의 크기를 기록해 두는 방식을 쓰겠습니다. 물론 이런식으로 해결하면, 상당히 많은 오버헤드가 예상되지만, 어차피 이 모듈은 디버깅을 위한 것이기 때문에 괜찮습니다. 다섯번째 기능을, 시스템에 독립적인 코드로 완전히 해결하기란 사실상 불가능합니다. Xmalloc 모듈에서는, 요구한 블럭의 앞 뒤 부분에 특정 값을 가진 추가적인 블럭을 할당하고, 나중에 이 블럭을 되돌려 줄 때, 이 블럭의 값이 변경되었는지 검사하는 방법으로 이 문제를 해결합니다. 편의상 이 추가적인 블럭을 ‘redzone’이라고 부르기로 합니다. Figure 3.1를 보면 xmalloc()이 실제 malloc(3)에게 요구하는 블럭의 크기는 사용자가 요구한 블럭(‘requested block’)보다 큰 것을 알 수 있으며, 이 여분의 공간이 어떤 목적으로 쓰이는지 알 수 있습니다. 실제로 다음과 같은 타입을 만들어서 이 방법을 적용하려고 합니다 (아래 코드에서 struct xmemheader는 Figure 3.1의 ‘size’와 ‘redzone’을 위한 것이며, struct xmemfooter는 뒤에 붙는 redzone을 위한 것입니다):
struct xmemheader {
  size_t size;
  unsigned int redzone;
};

struct xmemfooter {
  unsigned int redzone[2];
};
일단 각각의 함수가 불려진 횟수를 기록하기 위해 다음과 같은 전역 변수가 준비됩니다:
static int malloc_called;
static int calloc_called;
static int realloc_called;
static int free_called;
또, 현재 동적으로 얻은 메모리의 크기를 저장하기 위한 ‘xmem_cur_size’와, 프로세스가 동적 메모리를 소비한 양이 가장 클 때의 값을 저장하기 위한 ‘xmem_max_size’, 가상으로 메모리 크기 제한을 두기 위한 ‘xmem_limit’을 준비합니다:
static long xmem_limit = -1;
static size_t xmem_cur_size;
static size_t xmem_max_size;
예상치 못했던 에러가 발생했을 경우, 에러 메시지 출력을 위한 스트림을 가리키는 ‘xmem_error_stream’, 그리고 에러가 발생했을 때 부를 에러 처리 함수를 위한 ‘xalloc_failed_handler’도 준비합니다. (이 두 변수는 적절한 초기값을 미리 정해 놓았습니다):
static FILE *xmem_error_stream = stderr;
static void (*xalloc_failed_handler)(void) = abort;

xmalloc()

아래는 xmalloc()의 실제 코드입니다. 몇 가지 아직 설명하지 않은 부분이 있지만, 먼저 xmalloc()이 어떤 식으로 동작하는지 알아둡시다:
void *
xmalloc(size_t size)
{
  void *ptr;
                                                                                
  if (size == 0)
    size = 1;
                                                                                
  ptr = malloc(ptr_size(size));
  if (!ptr || add_stat(size) < 0)
    goto err;
  malloc_called++;
  return set_ptr(ptr, size);
                                                                                
 err:
  if (xalloc_failed_handler)
    xalloc_failed_handler();
  return 0;
}
xmalloc()에 주어진 크기가 0인 경우, 우리는 일관성있는 동작을 얻기 위해1, 그 크기를 1로 변경합니다. 그 뒤에 malloc(3)을 불러 메모리를 얻고 – ptr_size()에 대해서는 뒤에 설명합니다 – add_stat()을 호출한 다음, ‘malloc_called’에 1을 더한 다음, set_ptr()의 리턴값을 돌려줍니다. 마찬가지로 add_stat()과 set_ptr()에 대해서는 뒤에서 설명합니다. 만약 malloc()이 널을 리턴했거나, add_stat()이 -1을 리턴한 경우, xalloc_failed_handler가 널이 아니면, 이 함수 포인터가 가리키는 함수를 불러서 에러를 처리합니다. 에러 처리 함수가 등록되어 있지 않은 경우나 – xalloc_failed_handler가 널인 경우 – 에러 처리 함수가 프로세스를 종료시키지 않더라도 xmalloc()은 올바르게 널을 리턴하는 것을 알 수 있습니다. 에러 처리 함수가 리턴된 다음에도, 널을 리턴하지 않고, 엉망으로 코드가 흘러가지 않도록 조심해야 합니다. ptr_size(), set_ptr()은 Figure 3.1에서 설명한 것처럼 redzone 처리를 담당합니다. 일단 ptr_size()는 사용자가 요청한 메모리 사이즈에, Figure 3.1의 ‘size’를 위한 공간 더하기 앞뒤의 redzone을 위한 크기를 더한 값을 돌려줍니다:
static __inline__ size_t
ptr_size(size_t size)
{
  size += sizeof(struct xmemheader);
  size += sizeof(struct xmemfooter);
  return size;
}
setptr()은 Figure 3.1에 나온 것처럼 redzone과 size를 초기화하며, 실제 할당한 메모리 블럭의 주소를 받아서, 사용자에게 돌려줄 주소를 계산하여 돌려줍니다. (아래 코드에서 쓴 XMEM_HEADER와 XMEM_FOOTER는 단순한 어떤 특정 값입니다. ???의 소스를 참고 바랍니다):
static __inline__ void *
set_ptr(void *ptr, size_t size)
{
  struct xmemheader head;
  head.redzone = XMEM_HEADER;
  head.size = size;
  memcpy(ptr, &head, sizeof(head));
  static struct xmemfooter foot = { { XMEM_FOOTER, XMEM_FOOTER } };
  memcpy((char *)ptr + sizeof(head) + size, &foot, sizeof(foot));
  return (char *)ptr + sizeof(head);
}
마지막으로 add_stat()은 현재까지 할당한 메모리 블럭들의 총 합과, 최대로 할당했던 메모리 양을 계산하며, 만약 ‘xmem_limit’가 양수로 설정되어 있을 경우, 현재 할당한 블럭의 총 합이 이 값을 넘지 않는 지 검사합니다:
static __inline__ int
add_stat(size_t size)
{
  if (xmem_limit > 0 && xmem_cur_size + size > xmem_limit)
    return -1;
  xmem_cur_size += size;
  if (xmem_max_size < xmem_cur_size)
    xmem_max_size = xmem_cur_size;
  return 0;
}

xcalloc()

xmalloc()과는 달리, xcalloc()은 calloc(3)을 부르지 않고, malloc()을 불러서 calloc(3)의 역할을 합니다. 앞에서 설명한 xmalloc()을 다 이해했고, calloc(3)을 알고 있다면 아래 소스를 파악하는데 큰 어려움은 없습니다.
void *
xcalloc(size_t nmemb, size_t size)
{
  void *ptr;
                                                                                
  size = size * nmemb;
  if (size == 0)
    size = 1;
  ptr = malloc(ptr_size(size));
  if (!ptr || add_stat(size) < 0)
    goto err;
  calloc_called++;
  return set_ptr(ptr, size);
                                                                                
 err:
  if (xalloc_failed_handler)
    xalloc_failed_handler();
  return 0;
}

xrealloc()

아래는 xrealloc()의 소스입니다. 마찬가지로 크게 어렵지 않습니다.
void *
xrealloc(void *ptr, size_t size)
{
  if (size == 0)
    size = 1;
  ptr = get_ptr(ptr);
  ptr = realloc(ptr, ptr_size(size));
  if (!ptr || add_stat(size) < 0)
    goto err;
  realloc_called++;
  return set_ptr(ptr, size);
                                                                                
 err:
  if (xalloc_failed_handler)
    xalloc_failed_handler();
  return 0;
}

xfree()

free()의 경우, 앞에서 설명한, 메모리를 할당하는 함수들과는 달리, 좀 다른 작업을 먼저 해야 합니다. Figure 3.1에서 설명한 redzone이, 원래 설정했던 값으로 제대로 설정되어 있는지, ...




xmalloc.h

/* $Id: xmalloc,v 1.6 2005/03/22 06:10:28 kss Exp kss $ */
/*
 * malloc debugger
 * Copyright (C) 2004  Seong-Kook Shin <cinsk.shin at samsung.com>
 */
#ifndef XMALLOC_H_
#define XMALLOC_H_
                                                                                            
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
                                                                                            
#include <stddef.h>
                                                                                            
/* This indirect using of extern "C" { ... } makes Emacs happy */
#ifndef BEGIN_C_DECLS
# ifdef __cplusplus
#  define BEGIN_C_DECLS extern "C" {
#  define END_C_DECLS   }
# else
#  define BEGIN_C_DECLS
#  define END_C_DECLS
# endif
#endif /* BEGIN_C_DECLS */
                                                                                            
BEGIN_C_DECLS
                                                                                            
#define XMEM_STAT
#define XMEM_REDZONE
 
#ifndef NDEBUG
extern void *xmalloc(size_t size);
extern void *xcalloc(size_t nmemb, size_t size);
extern void *xrealloc(void *ptr, size_t size);
extern void xfree(void *ptr);
extern void xmemstat(memstat_t *ms);
extern long xmemopt(int option, ...);
#else
# define xmalloc(size)          malloc(size)
# define xcalloc(nmemb, size)   calloc(nmemb, size)
# define xrealloc(ptr, size)    realloc(ptr, size)
# define xfree(ptr)             free(ptr)
# define xmemstat(ms)           ((void)0)
# define xmemopt(opt, val)      ((long)-1)
#endif /* NDEBUG */
                                                                                            
struct memstat_ {
  int malloc_called;
  int calloc_called;
  int realloc_called;
  int free_called;
                                                                                            
  size_t cur_size;
  size_t max_size;
  ssize_t limit;
};
typedef struct memstat_ memstat_t;
                                                                                            
enum {
  X_SETLIM,                     /* Set memory limit */
  X_GETLIM,                     /* Get memory limit */
  X_SETFH,                      /* Set the failed handler */
  X_GETFH,                      /* Get the failed handler */
  X_SETES,                      /* Set the error stream */
  X_GETES,                      /* Get the error stream */
};
                                                                                            
END_C_DECLS
                                                                                            
#endif /* XMALLOC_H_ */

xmalloc.c

/* $Id: xmalloc,v 1.6 2005/03/22 06:10:28 kss Exp kss $ */
/*
 * malloc debugger
 * Copyright (C) 2004  Seong-Kook Shin <cinsk.shin at samsung.com>
 */
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#include <xmalloc.h>
 
static long xmem_limit = -1;
static int malloc_called;
static int calloc_called;
static int realloc_called;
 
static int free_called;
static size_t xmem_cur_size;
static size_t xmem_max_size;
static FILE *xmem_error_stream = stderr;
static void (*xalloc_failed_handler)(void) = abort;
 
#define XMEM_HEADER ((0x2B2B56 << 16) + 0x1F6781)
#define XMEM_FOOTER 0xdeadbeef
 
struct xmemheader {
  size_t size;
  unsigned int redzone;
};
 
struct xmemfooter {
  unsigned int redzone[2];
};
 
 
static __inline__ size_t
ptr_size(size_t size)
{
#ifdef XMEM_STAT
  size += sizeof(struct xmemheader);
#endif
 
 
#ifdef XMEM_REDZONE
  size += sizeof(struct xmemfooter);
#endif
                                                                                            
  return size;
}
                                                                                            
                                                                                            
static __inline__ void *
set_ptr(void *ptr, size_t size)
{
#ifdef XMEM_STAT
  struct xmemheader head;
  head.redzone = XMEM_HEADER;
  head.size = size;
  memcpy(ptr, &head, sizeof(head));
#endif
                                                                                            
#ifdef XMEM_REDZONE
  {
    static struct xmemfooter foot = { { XMEM_FOOTER, XMEM_FOOTER } };
    memcpy((char *)ptr + sizeof(head) + size, &foot, sizeof(foot));
  }
#endif
                                                                                            
#ifdef XMEM_STAT
  return (char *)ptr + sizeof(head);
#else
  return ptr;
                                                                                            
                                                                                            
#endif /* XMEM_STAT */
}
                                                                                            
                                                                                            
static __inline__ void *
get_ptr(void *ptr)
{
#ifdef XMEM_STAT
  return (char *)ptr - sizeof(struct xmemheader);
#else
  return (char *)ptr;
#endif
}
                                                                                            
                                                                                            
static __inline__ int
add_stat(size_t size)
{
  if (xmem_limit > 0 && xmem_cur_size + size > xmem_limit)
    return -1;
  xmem_cur_size += size;
  if (xmem_max_size < xmem_cur_size)
    xmem_max_size = xmem_cur_size;
  return 0;
}
                                                                                            
                                                                                            
void *
xmalloc(size_t size)
{
  void *ptr;
                                                                                            
  if (size == 0)
    size = 1;
                                                                                            
  ptr = malloc(ptr_size(size));
  if (!ptr || add_stat(size) < 0)
    goto err;
  malloc_called++;
  return set_ptr(ptr, size);
                                                                                            
 err:
  if (xalloc_failed_handler)
    xalloc_failed_handler();
  return 0;
}
                                                                                            
                                                                                            
void *
xcalloc(size_t nmemb, size_t size)
{
  void *ptr;
                                                                                            
  size = size * nmemb;
  if (size == 0)
    size = 1;
  ptr = malloc(ptr_size(size));
  if (!ptr || add_stat(size) < 0)
    goto err;
  calloc_called++;
  return set_ptr(ptr, size);
                                                                                            
 err:
  if (xalloc_failed_handler)
    xalloc_failed_handler();
  return 0;
}
                                                                                            
                                                                                            
void *
xrealloc(void *ptr, size_t size)
{
  if (size == 0)
    size = 1;
  ptr = get_ptr(ptr);
  ptr = realloc(ptr, ptr_size(size));
  if (!ptr || add_stat(size) < 0)
    goto err;
  realloc_called++;
  return set_ptr(ptr, size);
                                                                                            
 err:
  if (xalloc_failed_handler)
    xalloc_failed_handler();
  return 0;
}
                                                                                            
                                                                                            
static __inline__ int
redzone_check(void *ptr, size_t size)
{
#ifdef XMEM_REDZONE
  int i = 0;
  struct xmemfooter *p = (struct xmemfooter *)((char *)ptr +
                                               sizeof(struct xmemheader) +
                                               size);
                                                                                            
  while (i < sizeof(p->redzone) / sizeof(int)) {
    if (p->redzone[i] != XMEM_FOOTER)
      return -1;
    i++;
  }
#endif /* XMEM_REDZONE */
  return 0;
}
                                                                                            
                                                                                            
void
xfree(void *ptr)
{
#ifdef XMEM_STAT
  {
    struct xmemheader *p;
    p = (struct xmemheader *)get_ptr(ptr);
    if (p->redzone != XMEM_HEADER) {
      /* Invalid usage of xfree(), PTR wasn't generated by xmalloc() */
      if (xmem_error_stream)
        fprintf(xmem_error_stream,
                "error: xfree() failed: redzone is not valid\n");
      return;
    }
                                                                                            
    xmem_cur_size -= p->size;
    if (redzone_check(p, p->size) < 0 && xalloc_failed_handler)
      xalloc_failed_handler();
  }
#endif
                                                                                            
  free_called++;
  free(get_ptr(ptr));
}
                                                                                            
                                                                                            
long
xmemopt(int option, ...)
{
  va_list ap;
                                                                                            
  switch (option) {
#ifdef XMEM_STAT
  case X_SETLIM:
    va_start(ap, option);
    xmem_limit = va_arg(ap, long);
    va_end(ap);
    break;
  case X_GETLIM:
    return xmem_limit;
#endif /* XMEM_STAT */
  case X_SETES:
    va_start(ap, option);
    xmem_error_stream = va_arg(ap, FILE *);
    va_end(ap);
    break;
  case X_GETES:
    return (long)xmem_error_stream;
  case X_SETFH:
    va_start(ap, option);
    xalloc_failed_handler = va_arg(ap, void (*)(void));
    va_end(ap);
    break;
  case X_GETFH:
    return (long)xalloc_failed_handler;
                                                                                            
  default:
    return -1;
  }
  return 0;
}
                                                                                            
void
xmemstat(memstat_t *ms)
{
  ms->malloc_called = malloc_called;
  ms->calloc_called = calloc_called;
  ms->realloc_called = realloc_called;
  ms->free_called = free_called;
                                                                                            
  ms->cur_size = xmem_cur_size;
  ms->max_size = xmem_max_size;
  ms->limit = xmem_limit;
}
                                                                                            
                                                                                            
#ifdef TEST_XMALLOC
#include <stdio.h>
                                                                                            
int
main(void)
{
  memstat_t ms;
  char *vec[80];
  int i, j;
                                                                                            
  xmemopt(X_SETLIM, 10000);
  xmemopt(X_SETFH, abort);
                                                                                            
  for (i = 0; i < 80; i++) {
    int size = rand() % 1000;
    printf("xmalloc(%d) called..\n", size);
    vec[i] = xmalloc(size);
    for (j = 0; j < size; j++) {
      vec[i][j] = '*';
    }
  }
                                                                                            
  for (i = 0; i < 60; i++) {
    int size = rand() % 1000;
    printf("xrealloc(0x%08X, %d) called..\n", (int)vec[i], size);
    vec[i] = xrealloc(vec[i], size);
    for (j = 0; j < size; j++) {
      vec[i][j] = '*';
    }
  }
                                                                                            
  for (i = 0; i < 80; i++) {
    xfree(vec[i]);
  }
                                                                                            
  xmemstat(&ms);
  return 0;
}
#endif /* TEST_XMALLOC */





sponsored by andamiro
sponsored by cdnetworks
sponsored by HP

Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2005-03-22 15:10:28
Processing time 0.0179 sec