· 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 */



ID
Password
Join
It is Fortune, not wisdom that rules man's life.


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.0107 sec