OpenSSL学习笔记——内存分配

来源:互联网 发布:radium mac 编辑:程序博客网 时间:2024/06/11 03:10

环境:OpenSSL 0.9.8l,Fedora 12

  今天学习的是《OpenSSL编程》第五章 内存分配。这一章主要讲了OpenSSL在内存分配时,是如何做的。在C语言中,一般使用的是malloc和free来申请和释放内存的。当然,在OpenSSL中,我们同样也可以使用前面两个函数的使用内存。不过,OpenSSL也为我们提供了一些更强大的内存管理函数。这些函数主要强大在,可以方便地检查内存的泄露点,可以知道某一次对内存的申请造成了多大的内存泄露,是哪个文件,哪行语句造成的。如果在程序中完全使用OpenSSL为我们提供的这些函数,我们就可以方便地找到内存泄露点。
  在开始之前,得先介绍几个变量:
  LHASH *mh = NULL;//这个哈希表是用来存内存分配请求的,每请求一次内存,就给里面添一项,把内存释放时,就把相应的那一项去掉。key是MEM::addr。
  LHASH *amih = NULL;//这个哈希表是用来存APP_INFO的。key是APP_INFO::thread。
  下面,是在OpenSSL在内存方面使用的数据结构:
  typedef struct app_mem_info_st
  {   
   unsigned long thread;  
//申请内存的线程号
   const char *file;  
//申请内存的文件名
   int line;        
  //申请内存的行号
   const char *info;
   struct app_mem_info_st *next;
/* tail of thread's stack */
   int references;  
//被引用的次数
  } APP_INFO;

  这个结构体是用来记录用户申请的内存信息的。

  typedef struct mem_st
  {
   void *addr;         
   //分配的内存的地址
   int num;           
    //分配内存的大小
   const char *file;    
  //申请内存的文件名
   int line;            
  //申请内存的行号
   unsigned long thread; 
//申请内存的线程号
   unsigned long order;  
//申请有内存的次数
   time_t time;          
//申请内存的时间
   APP_INFO *app_info;   
//相关信息
  } MEM;

  这个结构是用来描述内存块的,用户每使用OPENSSL_malloc成功申请一次内存,就会产生一个这样的结构体,并把这个结构体存入哈希表mh中。
  
  下面,我想从两个函数(OPENSSL_malloc和OPENSSL_free)来说明OpenSSL在内存管理方面是怎么做的,其实OpenSSL只是给我们常用的malloc和free加了一个外壳,这使得用户在申请内存时,它可以把用户的信息记录下来并存入一个哈希表中。
  
函数OPENSSL_maloc
  函数OPENSSL_malloc是OpenSSL中在申请内存时,用来代替malloc的。其实,OPENSSL_malloc是一个宏定义,定义在文件/cypto/crypto.h中:
  #define OPENSSL_malloc(num)    CRYPTO_malloc((int)num,__FILE__,__LINE__)
  从这里,很明显可以看出,当我们调用OPENSSL_malloc时,OpenSSL可以通过宏__FILE__和__LINE__很方便地把文件名和行号录下来。那么,函数CRYPTO_malloc又是什么呢?它是一个函数,定义在文件/crypto/mem.c中:
  void *CRYPTO_malloc(int num, const char *file, int line)
  {
   void *ret = NULL;
   extern unsigned char cleanse_ctr;  
//值为0

   if (num <= 0) return NULL;

   allow_customize = 0;
   if (malloc_debug_func != NULL)
   {
    allow_customize_debug = 0;
    malloc_debug_func(NULL, num, file, line, 0);
   }
   ret = malloc_ex_func(num,file,line);  
//其实内存就是在这一句被分配的
#ifdef LEVITTE_DEBUG_MEM
   fprintf(stderr, "LEVITTE_DEBUG_MEM:         > 0x%p (%d)/n", ret, num);
#endif
   if (malloc_debug_func != NULL)
    malloc_debug_func(ret, num, file, line, 1);

    
    /* Create a dependency on the value of 'cleanse_ctr' so our memory
         * sanitisation function can't be optimised out. NB: We only do
         * this for >2Kb so the overhead doesn't bother us. */

   if(ret && (num > 2048))
    ((unsigned char *)ret)[0] = cleanse_ctr;

   return ret;
  }
  从上的函数,我们可以看出内存分配是通过malloc_ex_func完成的,在这个函数的上下方,分别调用了两次malloc_debug_func。
  我们先说malloc_ex_func,它是一个函数指针,它的定义在文件/crypto/mem.c中:
  static void *(*malloc_func)(size_t)         = malloc;
  static void *default_malloc_ex(size_t num, const char *file, int line)
      { return malloc_func(num); }
  static void *(*malloc_ex_func)(size_t, const char *file, int line)
          = default_malloc_ex;

  其实,函数malloc_ex_func就是malloc。
  我们再来看函数,malloc_debug_func,它也是一个函数指针,它的定义在文件/crypto/mem.c中:
  static void (*malloc_debug_func)(void *,int,const char *,int,int) = NULL;
  可以看到,这个函数指针的初值为0,也就是说,如果用户在编程时没有改变它的值,函数CRYPTO_malloc就相当于是函数malloc,此时,OpenSSL不提供内存泄露检查的功能。要使用OpenSSL的内存泄露检查功能时,得在程序开始时调用函数CRYPTO_malloc_debug_init。这个函数定义在文件/crypto/mem_dbg.c中:
  void CRYPTO_malloc_debug_init(void)
  {
   CRYPTO_set_mem_debug_functions(
                                        CRYPTO_dbg_malloc,
                                        CRYPTO_dbg_realloc,
                                        CRYPTO_dbg_free,
                                        CRYPTO_dbg_set_options,
                                        CRYPTO_dbg_get_options);
   CRYPTO_set_mem_info_functions(
                                        CRYPTO_dbg_push_info,
                                        CRYPTO_dbg_pop_info,
                                        CRYPTO_dbg_remove_all_info);
  }
  函数CRYPTO_set_mem_debug_functions定义在文件/crypto/mem.c中:
  int CRYPTO_set_mem_debug_functions(void (*m)(void *,int,const char *,int,int),
                                                                               void (*r)(void *,void *,int,const char *,int,int),
                                                                                void (*f)(void *,int),
                                                                                 void (*so)(long),
                                                                                long (*go)(void))
  {
   if (!allow_customize_debug)
    return 0;
   malloc_debug_func=m;
   realloc_debug_func=r;
   free_debug_func=f;
   set_debug_options_func=so;
   get_debug_options_func=go;
   return 1;
  }
  从上面两个函数可以看出,当用户调用函数CRYPTO_malloc_debug_init以后,这些相应的函数指针就被赋上的相应的值。下面,我们需要看一下函数CRYPTO_dbg_malloc的实现,这个函数的实现在文件/crypto/mem_dbg.c中(省略了一些不重要的代码):
  static unsigned long break_order_num=0;
  void CRYPTO_dbg_malloc(void *addr, int num, const char *file, int line, int before_p)
  {
   MEM *m,*mm;
   APP_INFO tmp,*amim;

   switch(before_p & 127)
   {
    case 0:
     break;
    case 1:
     if (addr == NULL)
      break;

     if (is_MemCheck_on())
     {
      ...

    
  //把用户申请内存的信息存入结构体m中
      m->addr=addr;
      m->file=file;
      m->line=line;
      m->num=num;
      if (options & V_CRYPTO_MDEBUG_THREAD)
       m->thread=CRYPTO_thread_id();
      else
       m->thread=0;

      if (order == break_order_num)
      {
     
  /* BREAK HERE */
       m->order=order;
      }
      m->order=order++;
         
          ...

      if (options & V_CRYPTO_MDEBUG_TIME)
       m->time=time(NULL);
      else
       m->time=0;

      tmp.thread=CRYPTO_thread_id();
      m->app_info=NULL;
      if (amih != NULL
        && (amim=(APP_INFO *)lh_retrieve(amih,(char *)&tmp)) != NULL)
      {
//在哈希表amih中查找,是否存在与当前线程相关的APP_INFO
       m->app_info = amim;
       amim->references++; 
 //引用数加1
      }

      if ((mm=(MEM *)lh_insert(mh,(char *)m)) != NULL)
      {
//把结构体m插入到哈希表mh中去。如果返回值不是NULL,则说明mh中原来就有一个MEM的addr与传进来的addr相同。
       /* Not good, but don't sweat it */
       if (mm->app_info != NULL)
       {
        mm->app_info->references--;
       }
       OPENSSL_free(mm);
      }
   err:
      MemCheck_on();
/* release MALLOC2 lock
                        
                                * if num_disabled drops to 0 */
     }
     break;
    }
   return;
  }
  看完上面的代码,应该不难理解函数OPENSSL_malloc的执行过程了,通过函数malloc_ex_func(也就是malloc)分配内存,通过函数CRYPTO_malloc_dbg把用户申请内存的信息存入哈希表mh中。

  
函数OPENSSL_free
  函数OPENSSL_free是OpenSSL在释放内存时,用来代替free的。OPENSSL_free是一个宏定义,定义在文件/crypto/crypto.h中:
  #define OPENSSL_free(addr)    CRYPTO_free(addr)
  我们再来看一下函数CRYPTO_free,实现在文件/crypto/mem.c中:
  void CRYPTO_free(void *str)
  {
   if (free_debug_func != NULL)
    free_debug_func(str, 0);
#ifdef LEVITTE_DEBUG_MEM
   fprintf(stderr, "LEVITTE_DEBUG_MEM:         < 0x%p/n", str);
#endif
   free_func(str);
   if (free_debug_func != NULL)
    free_debug_func(NULL, 1);
  }
  这里,我们可以看到函数CRYPTO_free和函数CRYPTO_malloc的结构几乎是一样的。变量free_debug_func和变量free_func是两个函数指针,定义在文件/crypto/mem.c中:
  static void (*free_func)(void *)            = free;
  static void (*free_debug_func)(void *,int) = NULL;

  从函数CRYPTO_malloc_debug_init和函数CRYPTO_set_mem_debug_functions中可以看出,变量free_debug_func的值应该为CRYPTO_dbg_free,这个函数的定义在文件/crypto/mem_dbg.c中:
  void CRYPTO_dbg_free(void *addr, int before_p)
  {
   MEM m,*mp;

   switch(before_p)
   {
   case 0:
    if (addr == NULL)
     break;

    if (is_MemCheck_on() && (mh != NULL))
    {
     MemCheck_off();
/* make sure we hold MALLOC2 lock */

     m.addr=addr;
     mp=(MEM *)lh_delete(mh,(char *)&m);
//在哈希表mh中删除分配内存首地址为addr的MEM
     if (mp != NULL)
     {
//如果不为NULL,则说明上一句lh_delete删除成功,下面就应该删除与之对应的APP_INFO
#ifdef LEVITTE_DEBUG_MEM
      fprintf(stderr, "LEVITTE_DEBUG_MEM: [%5d] - 0x%p (%d)/n",
            mp->order, mp->addr, mp->num);
#endif
      if (mp->app_info != NULL)
       app_info_free(mp->app_info);
      OPENSSL_free(mp);
     }

     MemCheck_on();
/* release MALLOC2 lock
                                                    * if num_disabled drops to 0 */

    }
    break;
   case 1:
    break;
   }
  }
  所以,当我们调用OPENSSL_malloc申请内存,而没有调用OPENSSL_free释放内存时,我们只需要遍历哈希表mh,就可以知道哪些内存还没有被释放。
  用于输出内存泄露信息的函数,我们可以使用CRYPTO_mem_leaks,这个函数把内存泄露的信息输出到一个BIO。

  下面,我们来看一下OpenSSL一些常用的内存管理的函数:
  1. #define OPENSSL_malloc(num)  CRYPTO_malloc((int)num,__FILE__,__LINE__)
  2. #define OPENSSL_strdup(str)  CRYPTO_strdup((str),__FILE__,__LINE__)

   char *CRYPTO_strdup(const char *str, const char *file, int line)
   {
    char *ret = CRYPTO_malloc(strlen(str)+1, file, line);

    strcpy(ret, str);
    return ret;
   }
  3. #define OPENSSL_realloc(addr,num)  CRYPTO_realloc((char *)addr,(int)num,__FILE__,__LINE__)
   此函数用法与realoc相同。
  4. #define OPENSSL_free(addr)    CRYPTO_free(addr)
  5. void CRYPTO_mem_leaks_fp(FILE *fp);

   此函数的作用是把内存泄露信息输出到文件fp,这个函数是通过CRYPTO_mem_leaks实现的。
  6. void CRYPTO_mem_leaks(struct bio_st *bio);
   此函数的作用是把内存泄露信息输出到bio。
  7. void CRYPTO_malloc_debug_init(void);

原创粉丝点击