2010-05-14 6 views
11

Este es un seguimiento de Critique my heap debugger de ayer. Como sugiere bitc, ahora guardo los metadatos sobre los bloques asignados en una tabla de claves manuscrita separada.Critica mi depurador de montón no intrusivo

El depurador montón ahora detecta los siguientes tipos de errores:

  1. pérdidas de memoria (ahora con salida de depuración más detallada)
  2. punteros ilegales pasaron a eliminar (que también se encarga de eliminaciones dobles)
  3. forma equivocada de borrado (matriz frente a no-array)
  4. desbordamientos de búfer
  5. búfer subdesborda

Siéntase libre de discutir y gracias de antemano!

#include <cstdio> 
#include <cstdlib> 
#include <cstring> 
#include <new> 

namespace 
{ 
    // I don't want to #include <algorithm> for a single function template :) 
    template <typename T> 
    void my_swap(T& x, T& y) 
    { 
     T z(x); 
     x = y; 
     y = z; 
    } 

    typedef unsigned char byte; 

    const byte CANARY[] = {0x5A, 0xFE, 0x6A, 0x8D, 
          0x5A, 0xFE, 0x6A, 0x8D, 
          0x5A, 0xFE, 0x6A, 0x8D, 
          0x5A, 0xFE, 0x6A, 0x8D}; 

    bool canary_dead(const byte* cage) 
    { 
     bool dead = memcmp(cage, CANARY, sizeof CANARY); 
     if (dead) 
     { 
      for (size_t i = 0; i < sizeof CANARY; ++i) 
      { 
       byte b = cage[i]; 
       printf(b == CANARY[i] ? "__ " : "%2X ", b); 
      } 
      putchar('\n'); 
     } 
     return dead; 
    } 

    enum kind_of_memory {AVAILABLE, TOMBSTONE, NON_ARRAY_MEMORY, ARRAY_MEMORY}; 

    const char* kind_string[] = {0, 0, "non-array memory", " array memory"}; 

    struct metadata 
    { 
     byte* address; 
     size_t size; 
     kind_of_memory kind; 

     bool in_use() const 
     { 
      return kind & 2; 
     } 

     void print() const 
     { 
      printf("%s at %p (%d bytes)\n", kind_string[kind], address, size); 
     } 

     bool must_keep_searching_for(void* address) 
     { 
      return kind == TOMBSTONE || (in_use() && address != this->address); 
     } 

     bool canaries_alive() const 
     { 
      bool alive = true; 
      if (canary_dead(address - sizeof CANARY)) 
      { 
       printf("ERROR: buffer underflow at %p\n", address); 
       alive = false; 
      } 
      if (canary_dead(address + size)) 
      { 
       printf("ERROR:  buffer overflow at %p\n", address); 
       alive = false; 
      } 
      return alive; 
     } 
    }; 

    const size_t MINIMUM_CAPACITY = 11; 

    class hashtable 
    { 
     metadata* data; 
     size_t used; 
     size_t capacity; 
     size_t tombstones; 

    public: 

     size_t size() const 
     { 
      return used - tombstones; 
     } 

     void print() const 
     { 
      for (size_t i = 0; i < capacity; ++i) 
      { 
       if (data[i].in_use()) 
       { 
        printf(":(leaked "); 
        data[i].print(); 
       } 
      } 
     } 

     hashtable() 
     { 
      used = 0; 
      capacity = MINIMUM_CAPACITY; 
      data = static_cast<metadata*>(calloc(capacity, sizeof(metadata))); 
      tombstones = 0; 
     } 

     ~hashtable() 
     { 
      free(data); 
     } 

     hashtable(const hashtable& that) 
     { 
      used = 0; 
      capacity = 3 * that.size() | 1; 
      if (capacity < MINIMUM_CAPACITY) capacity = MINIMUM_CAPACITY; 
      data = static_cast<metadata*>(calloc(capacity, sizeof(metadata))); 
      tombstones = 0; 

      for (size_t i = 0; i < that.capacity; ++i) 
      { 
       if (that.data[i].in_use()) 
       { 
        insert_unsafe(that.data[i]); 
       } 
      } 
     } 

     hashtable& operator=(hashtable copy) 
     { 
      swap(copy); 
      return *this; 
     } 

     void swap(hashtable& that) 
     { 
      my_swap(data, that.data); 
      my_swap(used, that.used); 
      my_swap(capacity, that.capacity); 
      my_swap(tombstones, that.tombstones); 
     } 

     void insert_unsafe(const metadata& x) 
     { 
      *find(x.address) = x; 
      ++used; 
     } 

     void insert(const metadata& x) 
     { 
      if (2 * used >= capacity) 
      { 
       hashtable copy(*this); 
       swap(copy); 
      } 
      insert_unsafe(x); 
     } 

     metadata* find(void* address) 
     { 
      size_t index = reinterpret_cast<size_t>(address) % capacity; 
      while (data[index].must_keep_searching_for(address)) 
      { 
       ++index; 
       if (index == capacity) index = 0; 
      } 
      return &data[index]; 
     } 

     void erase(metadata* it) 
     { 
      it->kind = TOMBSTONE; 
      ++tombstones; 
     } 
    } the_hashset; 

    struct heap_debugger 
    { 
     heap_debugger() 
     { 
      puts("heap debugger started"); 
     } 

     ~heap_debugger() 
     { 
      the_hashset.print(); 
      puts("heap debugger shutting down"); 
     } 
    } the_heap_debugger; 

    void* allocate(size_t size, kind_of_memory kind) throw (std::bad_alloc) 
    { 
     byte* raw = static_cast<byte*>(malloc(size + 2 * sizeof CANARY)); 
     if (raw == 0) throw std::bad_alloc(); 

     memcpy(raw, CANARY, sizeof CANARY); 
     byte* payload = raw + sizeof CANARY; 
     memcpy(payload + size, CANARY, sizeof CANARY); 

     metadata md = {payload, size, kind}; 
     the_hashset.insert(md); 
     printf("allocated "); 
     md.print(); 
     return payload; 
    } 

    void release(void* payload, kind_of_memory kind) throw() 
    { 
     if (payload == 0) return; 

     metadata* p = the_hashset.find(payload); 

     if (!p->in_use()) 
     { 
      printf("ERROR: no dynamic memory at %p\n", payload); 
     } 
     else if (p->kind != kind) 
     { 
      printf("ERROR:wrong form of delete at %p\n", payload); 
     } 
     else if (p->canaries_alive()) 
     { 
      printf("releasing "); 
      p->print(); 
      free(static_cast<byte*>(payload) - sizeof CANARY); 
      the_hashset.erase(p); 
     } 
    } 
} 

void* operator new(size_t size) throw (std::bad_alloc) 
{ 
    return allocate(size, NON_ARRAY_MEMORY); 
} 

void* operator new[](size_t size) throw (std::bad_alloc) 
{ 
    return allocate(size, ARRAY_MEMORY); 
} 

void operator delete(void* payload) throw() 
{ 
    release(payload, NON_ARRAY_MEMORY); 
} 

void operator delete[](void* payload) throw() 
{ 
    release(payload, ARRAY_MEMORY); 
} 

int main() 
{ 
    int* p = new int[1]; 
    delete p; // wrong form of delete 
    delete[] p; // ok 
    delete p; // no dynamic memory (double delete) 

    p = new int[1]; 
    p[-1] = 0xcafebabe; 
    p[+1] = 0x12345678; 
    delete[] p; // underflow and overflow prevent release 
       // p is not released, hence leak 
} 
+0

¿No podría haber editado la versión de ayer de esta pregunta en lugar de comenzar otra pregunta sobre el mismo tema? –

+1

@Paul R: Este es completamente diferente ya que podría funcionar. – UncleBens

+0

No utilice la especificación de excepción, simplemente ponga esto en comentario si quiere que la gente lo sepa :) –

Respuesta

4

Muy bueno, de hecho. Sus canarios realmente podrían revelar algunos casos reales de desbordamiento/desbordamiento (aunque no todos ellos, como señaló Matthieu).

Qué más. Es posible que tenga algunos problemas con una aplicación de subprocesos múltiples. Tal vez proteger el hashtable del acceso concurrente?

Ahora que registra todas las asignaciones y desasignaciones, puede (si lo desea) proporcionar más información sobre el programa que se está probando. ¿Podría ser interesante conocer el número total y promedio de asignaciones en un momento dado? El total, máximo, mínimo y promedio de bytes asignados, y la vida media de las asignaciones.

Si quiere comparar diferentes hilos, al menos con Pthreads puede identificarlos con pthread_self(). Este depurador de pila podría convertirse en una herramienta de análisis bastante útil.

2

¿Está utilizando un malloc muy débil que todavía no tiene este tipo de cosas incorporadas? Porque si está ahí, estás duplicando la sobrecarga con poca ganancia. Además, este tipo de sistema realmente duele cuando se hace una pequeña asignación de objetos o es ineficaz con ellos ya que las personas hacen 1 alloc y administran la memoria ellos mismos.

En lo que respecta al código, parece que hará lo que dice que hará y se ve bien diseñado y es fácil de leer. Pero, si va a tomarse la molestia de hacer esto, ¿por qué no capturar los flujos de su búfer por encima/por debajo de la fuente utilizando contenedores/punteros/operador []? De esa forma, puedes depurar en el momento del error en lugar de descubrir que algo malo ha ocurrido.

Existen eficiencias que estoy seguro que otros encontrarán, pero estas son solo algunas reflexiones después de revisar su código por unos minutos.

2

Me pregunto acerca de la detección de subdesbordamientos/desbordamientos.

Quiero decir, si tengo una matriz de 10 elementos, entonces parece que detectará si escribo en -1 y 10, pero ¿y si escribo en 20? Underflow o Overflow no se realizan necesariamente como parte de un desbordamiento de búfer (contiguo).

Además, ¿para qué sirve prevenir la liberación del bloque? Este bloque está (relativamente) bien, son los vecinos que (desafortunadamente) han corrompido.

De todos modos, me parece muy bien, aunque probablemente tendría más de un retorno por función porque no tiene sentido en la salida única. Usted parece más un programador de C que uno de C++ :)

+0

Desbordamiento y desbordamiento se detectan hasta 16 bytes en ambas direcciones. Si eso no es suficiente para ti, simplemente aumenta el tamaño de la matriz 'CANARY' ;-) – fredoverflow

Cuestiones relacionadas